1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 5 * the License. A copy of the License is located at 6 * 7 * http://aws.amazon.com/apache2.0 8 * 9 * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 11 * and limitations under the License. 12 */ 13 14 package software.amazon.awssdk.services.kms.endpoints.internal; 15 16 import java.util.ArrayList; 17 import java.util.Arrays; 18 import java.util.List; 19 import java.util.Objects; 20 import software.amazon.awssdk.annotations.SdkInternalApi; 21 22 @SdkInternalApi 23 public class GetAttr extends Fn { 24 public static final String ID = "getAttr"; 25 GetAttr(FnNode node)26 public GetAttr(FnNode node) { 27 super(node); 28 } 29 30 @Override eval(Scope<Value> scope)31 public Value eval(Scope<Value> scope) { 32 Value root = target().eval(scope); 33 List<Part> path; 34 try { 35 path = path(); 36 } catch (InnerParseError e) { 37 throw new RuntimeException(e); 38 } 39 for (Part part : path) { 40 root = part.eval(root); 41 } 42 return root; 43 } 44 45 public interface Part { 46 eval(Value container)47 Value eval(Value container); 48 49 final class Key implements Part { 50 private final Identifier key; 51 Key(Identifier key)52 public Key(Identifier key) { 53 this.key = key; 54 } 55 56 @Override toString()57 public String toString() { 58 return key.asString(); 59 } 60 of(String key)61 public static Key of(String key) { 62 return new Key(Identifier.of(key)); 63 } 64 65 @Override eval(Value container)66 public Value eval(Value container) { 67 return container.expectRecord().get(key); 68 } 69 key()70 public Identifier key() { 71 return key; 72 } 73 74 @Override equals(Object obj)75 public boolean equals(Object obj) { 76 if (obj == this) { 77 return true; 78 } 79 if (obj == null || obj.getClass() != this.getClass()) { 80 return false; 81 } 82 Key that = (Key) obj; 83 return Objects.equals(this.key, that.key); 84 } 85 86 @Override hashCode()87 public int hashCode() { 88 return key != null ? key.hashCode() : 0; 89 } 90 } 91 92 final class Index implements Part { 93 private final int index; 94 Index(int index)95 public Index(int index) { 96 this.index = index; 97 } 98 99 @Override eval(Value container)100 public Value eval(Value container) { 101 return container.expectArray().get(index); 102 } 103 104 @Override toString()105 public String toString() { 106 return String.format("[%s]", index); 107 } 108 index()109 public int index() { 110 return index; 111 } 112 113 @Override equals(Object obj)114 public boolean equals(Object obj) { 115 if (obj == this) { 116 return true; 117 } 118 if (obj == null || obj.getClass() != this.getClass()) { 119 return false; 120 } 121 Index that = (Index) obj; 122 return this.index == that.index; 123 } 124 125 @Override hashCode()126 public int hashCode() { 127 return index; 128 } 129 } 130 } 131 fromBuilder(Builder builder)132 private static GetAttr fromBuilder(Builder builder) { 133 return new GetAttr(FnNode.builder().fn("getAttr") 134 .argv(Arrays.asList(builder.target, Literal.fromStr(String.join(".", builder.path)))).build()); 135 } 136 builder()137 public static Builder builder() { 138 return new Builder(); 139 } 140 target()141 public Expr target() { 142 return expectTwoArgs().left(); 143 } 144 path()145 public List<Part> path() throws InnerParseError { 146 Expr right = expectTwoArgs().right(); 147 if (right instanceof Literal) { 148 Literal path = (Literal) right; 149 return parse(path.expectLiteralString()); 150 } else { 151 throw SourceException.builder().message("second argument must be a string literal").build(); 152 } 153 } 154 parse(String path)155 private static List<Part> parse(String path) throws InnerParseError { 156 String[] components = path.split("\\."); 157 List<Part> result = new ArrayList<>(); 158 for (String component : components) { 159 if (component.contains("[")) { 160 int slicePartIndex = component.indexOf("["); 161 String slicePart = component.substring(slicePartIndex); 162 if (!slicePart.endsWith("]")) { 163 throw new InnerParseError("Invalid path component: %s. Must end with `]`"); 164 } 165 try { 166 String number = slicePart.substring(1, slicePart.length() - 1); 167 int slice = Integer.parseInt(number); 168 if (slice < 0) { 169 throw new InnerParseError("Invalid path component: slice index must be >= 0"); 170 } 171 result.add(Part.Key.of(component.substring(0, slicePartIndex))); 172 result.add(new Part.Index(slice)); 173 } catch (NumberFormatException ex) { 174 throw new InnerParseError(String.format("%s could not be parsed as a number", slicePart)); 175 } 176 } else { 177 result.add(Part.Key.of(component)); 178 } 179 } 180 if (result.isEmpty()) { 181 throw new InnerParseError("Invalid argument to GetAttr: path may not be empty"); 182 } 183 return result; 184 } 185 186 @Override acceptFnVisitor(FnVisitor<T> visitor)187 public <T> T acceptFnVisitor(FnVisitor<T> visitor) { 188 return visitor.visitGetAttr(this); 189 } 190 191 @Override toString()192 public String toString() { 193 StringBuilder out = new StringBuilder(); 194 out.append(target()); 195 try { 196 for (Part part : path()) { 197 out.append("."); 198 out.append(part); 199 } 200 } catch (InnerParseError e) { 201 throw new RuntimeException(e); 202 } 203 return out.toString(); 204 } 205 206 @Override template()207 public String template() { 208 String target = ((Ref) this.target()).getName().asString(); 209 StringBuilder pathPart = new StringBuilder(); 210 211 List<Part> partList; 212 try { 213 partList = path(); 214 } catch (InnerParseError e) { 215 throw new RuntimeException(e); 216 } 217 for (int i = 0; i < partList.size(); i++) { 218 if (i != 0) { 219 if (partList.get(i) instanceof Part.Key) { 220 pathPart.append("."); 221 } 222 } 223 pathPart.append(partList.get(i).toString()); 224 } 225 return "{" + target + "#" + pathPart + "}"; 226 } 227 228 public static class Builder { 229 Expr target; 230 String path; 231 target(Expr target)232 public Builder target(Expr target) { 233 this.target = target; 234 return this; 235 } 236 path(String path)237 public Builder path(String path) { 238 this.path = path; 239 return this; 240 } 241 build()242 public GetAttr build() { 243 return GetAttr.fromBuilder(this); 244 } 245 } 246 } 247