1 /* 2 * Copyright (C) 2018 The Android Open Source Project 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 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.signature.cts; 17 18 import java.io.BufferedReader; 19 import java.io.InputStream; 20 import java.io.InputStreamReader; 21 import java.nio.ByteBuffer; 22 import java.util.Arrays; 23 import java.util.function.BiFunction; 24 import java.util.function.Function; 25 import java.util.regex.Matcher; 26 import java.util.regex.Pattern; 27 import java.util.stream.Stream; 28 import java.util.stream.StreamSupport; 29 import java.text.ParseException; 30 31 /** 32 * Parses an API definition given as a text file with DEX signatures of class 33 * members. Constructs a {@link DexApiDocumentParser.DexMember} for every class 34 * member. 35 * 36 * <p>The definition file is converted into a {@link Stream} of 37 * {@link DexApiDocumentParser.DexMember}. 38 */ 39 public class DexApiDocumentParser { 40 41 // Regex patterns which match DEX signatures of methods and fields. 42 // See comment by next() for more details. 43 private static final Pattern REGEX_FIELD = Pattern.compile("^(L[^>]*;)->(.*):(.*)$"); 44 private static final Pattern REGEX_METHOD = 45 Pattern.compile("^(L[^>]*;)->(.*)(\\(.*\\).*)$"); 46 47 // Estimate of the length of a line. 48 private static final int LINE_LENGTH_ESTIMATE = 100; 49 50 // Converter from String to DexMember. Cached here. 51 private static final Function<String, DexMember> DEX_MEMBER_CONVERTER = str -> { 52 try { 53 return parseLine(str, /* lineNum= */ -1); // No line info available. 54 } catch (ParseException e) { 55 throw new RuntimeException(e); 56 } 57 }; 58 private static final BiFunction<String, Integer, DexMember> DEX_MEMBER_LINE_NUM_CONVERTER = ( 59 str, lineNum) -> { 60 try { 61 return parseLine(str, lineNum); 62 } catch (ParseException e) { 63 throw new RuntimeException(e); 64 } 65 }; 66 parseAsStream(Object o)67 public Stream<DexMember> parseAsStream(Object o) { 68 if (o instanceof ByteBuffer) { 69 return parseAsStream((ByteBuffer) o); 70 } else { 71 return parseAsStream((InputStream) o); 72 } 73 } 74 parseAsStream(InputStream inputStream)75 public Stream<DexMember> parseAsStream(InputStream inputStream) { 76 BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 77 return StreamSupport.stream( 78 new BufferedReaderLineSpliterator<DexMember>(reader, DEX_MEMBER_LINE_NUM_CONVERTER), 79 false); 80 } 81 parseAsStream(ByteBuffer buffer)82 public Stream<DexMember> parseAsStream(ByteBuffer buffer) { 83 return parseAsStream(buffer, LINE_LENGTH_ESTIMATE); 84 } parseAsStream(ByteBuffer buffer, int lineLengthEstimate)85 public Stream<DexMember> parseAsStream(ByteBuffer buffer, int lineLengthEstimate) { 86 // TODO: Ensurance that the input conforms to ByteBufferLineSpliterator requirements. 87 return StreamSupport.stream(new ByteBufferLineSpliterator<DexMember>(buffer, 88 lineLengthEstimate, DEX_MEMBER_CONVERTER), true); 89 } 90 parseLine(String line, int lineNum)91 private static DexMember parseLine(String line, int lineNum) throws ParseException { 92 // Split the CSV line. 93 String[] splitLine = line.split(","); 94 String signature = splitLine[0]; 95 String[] flags = Arrays.copyOfRange(splitLine, 1, splitLine.length); 96 97 // Match line against regex patterns. 98 Matcher matchField = REGEX_FIELD.matcher(signature); 99 Matcher matchMethod = REGEX_METHOD.matcher(signature); 100 101 // Check that *exactly* one pattern matches. 102 int matchCount = (matchField.matches() ? 1 : 0) + (matchMethod.matches() ? 1 : 0); 103 if (matchCount == 0) { 104 throw new ParseException("Could not parse: \"" + line + "\"", lineNum); 105 } else if (matchCount > 1) { 106 throw new ParseException("Ambiguous parse: \"" + line + "\"", lineNum); 107 } 108 109 // Extract information from the signature. 110 if (matchField.matches()) { 111 return new DexField( 112 matchField.group(1), matchField.group(2), matchField.group(3), flags); 113 } else if (matchMethod.matches()) { 114 return new DexMethod( 115 matchMethod.group(1),matchMethod.group(2), matchMethod.group(3), flags); 116 } 117 throw new IllegalStateException(); 118 } 119 } 120