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