1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.codegen.internal; 17 18 import static java.util.stream.Collectors.toList; 19 20 import java.io.Closeable; 21 import java.io.File; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.UncheckedIOException; 25 import java.nio.file.Files; 26 import java.util.List; 27 import java.util.Map; 28 import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel; 29 import software.amazon.awssdk.codegen.model.intermediate.MapModel; 30 import software.amazon.awssdk.codegen.model.intermediate.MemberModel; 31 import software.amazon.awssdk.codegen.model.intermediate.Metadata; 32 import software.amazon.awssdk.codegen.model.intermediate.ShapeMarshaller; 33 import software.amazon.awssdk.codegen.model.intermediate.ShapeModel; 34 import software.amazon.awssdk.codegen.model.intermediate.ShapeType; 35 import software.amazon.awssdk.codegen.model.service.Input; 36 import software.amazon.awssdk.codegen.model.service.Operation; 37 import software.amazon.awssdk.codegen.model.service.ServiceMetadata; 38 import software.amazon.awssdk.codegen.model.service.ServiceModel; 39 import software.amazon.awssdk.codegen.model.service.Shape; 40 import software.amazon.awssdk.codegen.model.service.XmlNamespace; 41 import software.amazon.awssdk.utils.IoUtils; 42 import software.amazon.awssdk.utils.StringUtils; 43 44 public final class Utils { 45 Utils()46 private Utils() { 47 } 48 isScalar(Shape shape)49 public static boolean isScalar(Shape shape) { 50 // enums are treated as scalars in C2j. 51 return !(isListShape(shape) || isStructure(shape) || isMapShape(shape)); 52 } 53 isStructure(Shape shape)54 public static boolean isStructure(Shape shape) { 55 return shape.getType().equals("structure"); 56 } 57 isListShape(Shape shape)58 public static boolean isListShape(Shape shape) { 59 return shape.getType().equals("list"); 60 } 61 isMapShape(Shape shape)62 public static boolean isMapShape(Shape shape) { 63 return shape.getType().equals("map"); 64 } 65 isEnumShape(Shape shape)66 public static boolean isEnumShape(Shape shape) { 67 return shape.getEnumValues() != null; 68 } 69 isExceptionShape(Shape shape)70 public static boolean isExceptionShape(Shape shape) { 71 return shape.isException() || shape.isFault(); 72 } 73 isOrContainsEnumShape(Shape shape, Map<String, Shape> allShapes)74 public static boolean isOrContainsEnumShape(Shape shape, Map<String, Shape> allShapes) { 75 boolean isEnum = isEnumShape(shape); 76 boolean isMapWithEnumMember = isMapShape(shape) 77 && (isOrContainsEnumShape(allShapes.get(shape.getMapKeyType().getShape()), allShapes) 78 || isOrContainsEnumShape(allShapes.get(shape.getMapValueType().getShape()), allShapes)); 79 boolean isListWithEnumMember = isListShape(shape) 80 && isOrContainsEnumShape(allShapes.get(shape.getListMember().getShape()), allShapes); 81 82 return isEnum || isMapWithEnumMember || isListWithEnumMember; 83 } 84 isOrContainsEnum(MemberModel member)85 public static boolean isOrContainsEnum(MemberModel member) { 86 boolean isEnum = member.getEnumType() != null; 87 return isEnum || isMapWithEnumShape(member) || isListWithEnumShape(member); 88 } 89 isListWithEnumShape(MemberModel member)90 public static boolean isListWithEnumShape(MemberModel member) { 91 return member.isList() && member.getListModel().getListMemberModel().getEnumType() != null; 92 } 93 isMapWithEnumShape(MemberModel member)94 public static boolean isMapWithEnumShape(MemberModel member) { 95 return member.isMap() && (isMapKeyWithEnumShape(member.getMapModel()) || isMapValueWithEnumShape(member.getMapModel())); 96 } 97 isMapKeyWithEnumShape(MapModel mapModel)98 public static boolean isMapKeyWithEnumShape(MapModel mapModel) { 99 return mapModel.getKeyModel().getEnumType() != null; 100 } 101 isMapValueWithEnumShape(MapModel mapModel)102 public static boolean isMapValueWithEnumShape(MapModel mapModel) { 103 return mapModel.getValueModel().getEnumType() != null; 104 } 105 unCapitalize(String name)106 public static String unCapitalize(String name) { 107 if (name == null || name.trim().isEmpty()) { 108 throw new IllegalArgumentException("Name cannot be null or empty"); 109 } 110 StringBuilder sb = new StringBuilder(name.length()); 111 112 int i = 0; 113 do { 114 sb.append(Character.toLowerCase(name.charAt(i++))); 115 } while ((i < name.length() && Character.isUpperCase(name.charAt(i))) 116 // not followed by a lowercase character 117 && !(i < name.length() - 1 && Character.isLowerCase(name.charAt(i + 1)))); 118 119 sb.append(name.substring(i)); 120 121 return sb.toString(); 122 } 123 capitalize(String name)124 public static String capitalize(String name) { 125 if (name == null || name.trim().isEmpty()) { 126 throw new IllegalArgumentException("Name cannot be null or empty"); 127 } 128 return name.length() < 2 ? StringUtils.upperCase(name) : StringUtils.upperCase(name.substring(0, 1)) 129 + name.substring(1); 130 } 131 removeLeading(String str, String toRemove)132 public static String removeLeading(String str, String toRemove) { 133 if (str == null) { 134 return null; 135 } 136 if (str.startsWith(toRemove)) { 137 return str.substring(toRemove.length()); 138 } 139 return str; 140 } 141 removeTrailing(String str, String toRemove)142 public static String removeTrailing(String str, String toRemove) { 143 if (str == null) { 144 return null; 145 } 146 if (str.endsWith(toRemove)) { 147 return str.substring(0, str.length() - toRemove.length()); 148 } 149 return str; 150 } 151 152 /** 153 * * @param serviceModel Service model to get prefix for. 154 * * @return Prefix to use when writing model files (service and intermediate). 155 */ getFileNamePrefix(ServiceModel serviceModel)156 public static String getFileNamePrefix(ServiceModel serviceModel) { 157 return String.format("%s-%s", serviceModel.getMetadata().getEndpointPrefix(), serviceModel.getMetadata().getApiVersion()); 158 } 159 160 /** 161 * Converts a directory to a Java package name. 162 * 163 * @param directoryPath Directory to convert. 164 * @return Package name 165 */ directoryToPackage(String directoryPath)166 public static String directoryToPackage(String directoryPath) { 167 return directoryPath.replace('/', '.'); 168 } 169 170 /** 171 * Converts a Java package name to a directory. 172 * 173 * @param packageName Java package to convert. 174 * @return directory 175 */ packageToDirectory(String packageName)176 public static String packageToDirectory(String packageName) { 177 return packageName.replace('.', '/'); 178 } 179 getDefaultEndpointWithoutHttpProtocol(String endpoint)180 public static String getDefaultEndpointWithoutHttpProtocol(String endpoint) { 181 182 if (endpoint == null) { 183 return null; 184 } 185 if (endpoint.startsWith("http://")) { 186 return endpoint.substring("http://".length()); 187 } 188 if (endpoint.startsWith("https://")) { 189 return endpoint.substring("https://".length()); 190 } 191 return endpoint; 192 } 193 createDirectory(String path)194 public static File createDirectory(String path) { 195 if (isNullOrEmpty(path)) { 196 throw new IllegalArgumentException( 197 "Invalid path directory. Path directory cannot be null or empty"); 198 } 199 200 File dir = new File(path); 201 createDirectory(dir); 202 return dir; 203 } 204 createDirectory(File dir)205 public static void createDirectory(File dir) { 206 if (!(dir.exists())) { 207 try { 208 Files.createDirectories(dir.toPath()); 209 } catch (IOException e) { 210 throw new UncheckedIOException("Failed to create " + dir, e); 211 } 212 } 213 } 214 createFile(String dir, String fileName)215 public static File createFile(String dir, String fileName) throws IOException { 216 217 if (isNullOrEmpty(fileName)) { 218 throw new IllegalArgumentException( 219 "Invalid file name. File name cannot be null or empty"); 220 } 221 222 createDirectory(dir); 223 224 File file = new File(dir, fileName); 225 226 if (!(file.exists())) { 227 if (!(file.createNewFile())) { 228 throw new RuntimeException("Not able to create file . " 229 + file.getAbsolutePath()); 230 } 231 } 232 233 return file; 234 } 235 isNullOrEmpty(String str)236 public static boolean isNullOrEmpty(String str) { 237 return str == null || str.trim().isEmpty(); 238 } 239 closeQuietly(Closeable closeable)240 public static void closeQuietly(Closeable closeable) { 241 IoUtils.closeQuietly(closeable, null); 242 } 243 244 /** 245 * Return an InputStream of the specified resource, failing if it can't be found. 246 * 247 * @param location Location of resource 248 */ getRequiredResourceAsStream(Class<?> clzz, String location)249 public static InputStream getRequiredResourceAsStream(Class<?> clzz, String location) { 250 InputStream resourceStream = clzz.getResourceAsStream(location); 251 252 if (resourceStream == null) { 253 // Try with a leading "/" 254 if (!location.startsWith("/")) { 255 resourceStream = clzz.getResourceAsStream("/" + location); 256 } 257 if (resourceStream == null) { 258 throw new RuntimeException("Resource file was not found at location " + location); 259 } 260 } 261 262 return resourceStream; 263 } 264 265 /** 266 * Search for intermediate shape model by its c2j name. 267 * 268 * @return ShapeModel 269 * @throws IllegalArgumentException if the specified c2j name is not found in the intermediate model. 270 */ findShapeModelByC2jName(IntermediateModel intermediateModel, String shapeC2jName)271 public static ShapeModel findShapeModelByC2jName(IntermediateModel intermediateModel, String shapeC2jName) 272 throws IllegalArgumentException { 273 ShapeModel shapeModel = findShapeModelByC2jNameIfExists(intermediateModel, shapeC2jName); 274 if (shapeModel != null) { 275 return shapeModel; 276 } else { 277 throw new IllegalArgumentException( 278 shapeC2jName + " shape (c2j name) does not exist in the intermediate model."); 279 } 280 } 281 282 /** 283 * Search for intermediate shape model by its c2j name. 284 * 285 * @return ShapeModel or null if the shape doesn't exist (if it's primitive or container type for example) 286 */ findShapeModelByC2jNameIfExists(IntermediateModel intermediateModel, String shapeC2jName)287 public static ShapeModel findShapeModelByC2jNameIfExists(IntermediateModel intermediateModel, String shapeC2jName) { 288 for (ShapeModel shape : intermediateModel.getShapes().values()) { 289 if (shape.getC2jName().equals(shapeC2jName)) { 290 return shape; 291 } 292 } 293 return null; 294 } 295 296 /** 297 * Search for a shape model by its C2J name, excluding request and response shapes, which are not candidates to be members 298 * of another shape. 299 * 300 * @return ShapeModel or null if the shape doesn't exist (if it's primitive or container type for example) 301 */ findMemberShapeModelByC2jNameIfExists(IntermediateModel intermediateModel, String shapeC2jName)302 public static ShapeModel findMemberShapeModelByC2jNameIfExists(IntermediateModel intermediateModel, String shapeC2jName) { 303 ShapeModel candidate = null; 304 for (ShapeModel shape : intermediateModel.getShapes().values()) { 305 if (shape.getShapeType() != ShapeType.Request 306 && shape.getShapeType() != ShapeType.Response 307 && shape.getC2jName().equals(shapeC2jName)) { 308 if (candidate != null) { 309 throw new IllegalStateException("Conflicting candidates for member model with C2J name " + shapeC2jName + ": " 310 + candidate + " and " + shape); 311 } 312 candidate = shape; 313 } 314 } 315 return candidate; 316 } 317 findShapesByC2jName(IntermediateModel intermediateModel, String shapeC2jName)318 public static List<ShapeModel> findShapesByC2jName(IntermediateModel intermediateModel, String shapeC2jName) { 319 return intermediateModel.getShapes().values().stream().filter(s -> s.getC2jName().equals(shapeC2jName)).collect(toList()); 320 } 321 322 /** 323 * Create the ShapeMarshaller to the input shape from the specified Operation. 324 * The input shape in the operation could be empty. 325 */ createInputShapeMarshaller(ServiceMetadata service, Operation operation)326 public static ShapeMarshaller createInputShapeMarshaller(ServiceMetadata service, Operation operation) { 327 328 if (operation == null) { 329 throw new IllegalArgumentException( 330 "The operation parameter must be specified!"); 331 } 332 333 ShapeMarshaller marshaller = new ShapeMarshaller() 334 .withAction(operation.getName()) 335 .withVerb(operation.getHttp().getMethod()) 336 .withRequestUri(operation.getHttp().getRequestUri()); 337 Input input = operation.getInput(); 338 if (input != null) { 339 marshaller.setLocationName(input.getLocationName()); 340 // Pass the xmlNamespace trait from the input reference 341 XmlNamespace xmlNamespace = input.getXmlNamespace(); 342 if (xmlNamespace != null) { 343 marshaller.setXmlNameSpaceUri(xmlNamespace.getUri()); 344 } 345 } 346 if (Metadata.isNotRestProtocol(service.getProtocol())) { 347 marshaller.setTarget(StringUtils.isEmpty(service.getTargetPrefix()) ? 348 operation.getName() : 349 service.getTargetPrefix() + "." + operation.getName()); 350 } 351 return marshaller; 352 353 } 354 } 355