1// Copyright 2023 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package jar 16 17import ( 18 "android/soong/third_party/zip" 19 "bufio" 20 "hash/crc32" 21 "sort" 22 "strings" 23) 24 25const servicesPrefix = "META-INF/services/" 26 27// Services is used to collect service files from multiple zip files and produce a list of ServiceFiles containing 28// the unique lines from all the input zip entries with the same name. 29type Services struct { 30 services map[string]*ServiceFile 31} 32 33// ServiceFile contains the combined contents of all input zip entries with a single name. 34type ServiceFile struct { 35 Name string 36 FileHeader *zip.FileHeader 37 Contents []byte 38 Lines []string 39} 40 41// IsServiceFile returns true if the zip entry is in the META-INF/services/ directory. 42func (Services) IsServiceFile(entry *zip.File) bool { 43 return strings.HasPrefix(entry.Name, servicesPrefix) 44} 45 46// AddServiceFile adds a zip entry in the META-INF/services/ directory to the list of service files that need 47// to be combined. 48func (j *Services) AddServiceFile(entry *zip.File) error { 49 if j.services == nil { 50 j.services = map[string]*ServiceFile{} 51 } 52 53 service := entry.Name 54 serviceFile := j.services[service] 55 fh := entry.FileHeader 56 if serviceFile == nil { 57 serviceFile = &ServiceFile{ 58 Name: service, 59 FileHeader: &fh, 60 } 61 j.services[service] = serviceFile 62 } 63 64 f, err := entry.Open() 65 if err != nil { 66 return err 67 } 68 defer f.Close() 69 70 scanner := bufio.NewScanner(f) 71 for scanner.Scan() { 72 line := scanner.Text() 73 if line != "" { 74 serviceFile.Lines = append(serviceFile.Lines, line) 75 } 76 } 77 78 if err := scanner.Err(); err != nil { 79 return err 80 } 81 82 return nil 83} 84 85// ServiceFiles returns the list of combined service files, each containing all the unique lines from the 86// corresponding service files in the input zip entries. 87func (j *Services) ServiceFiles() []ServiceFile { 88 services := make([]ServiceFile, 0, len(j.services)) 89 90 for _, serviceFile := range j.services { 91 serviceFile.Lines = dedupServicesLines(serviceFile.Lines) 92 serviceFile.Lines = append(serviceFile.Lines, "") 93 serviceFile.Contents = []byte(strings.Join(serviceFile.Lines, "\n")) 94 95 serviceFile.FileHeader.UncompressedSize64 = uint64(len(serviceFile.Contents)) 96 serviceFile.FileHeader.CRC32 = crc32.ChecksumIEEE(serviceFile.Contents) 97 if serviceFile.FileHeader.Method == zip.Store { 98 serviceFile.FileHeader.CompressedSize64 = serviceFile.FileHeader.UncompressedSize64 99 } 100 101 services = append(services, *serviceFile) 102 } 103 104 sort.Slice(services, func(i, j int) bool { 105 return services[i].Name < services[j].Name 106 }) 107 108 return services 109} 110 111func dedupServicesLines(in []string) []string { 112 writeIndex := 0 113outer: 114 for readIndex := 0; readIndex < len(in); readIndex++ { 115 for compareIndex := 0; compareIndex < writeIndex; compareIndex++ { 116 if interface{}(in[readIndex]) == interface{}(in[compareIndex]) { 117 // The value at readIndex already exists somewhere in the output region 118 // of the slice before writeIndex, skip it. 119 continue outer 120 } 121 } 122 if readIndex != writeIndex { 123 in[writeIndex] = in[readIndex] 124 } 125 writeIndex++ 126 } 127 return in[0:writeIndex] 128} 129