| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.libcore.timezone.tzlookup; |
| |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.nio.file.Paths; |
| import java.text.ParseException; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| |
| /** |
| * A class that knows about the structure of the backward file. |
| */ |
| final class BackwardFile { |
| |
| private final Map<String, String> links = new HashMap<>(); |
| |
| private BackwardFile() {} |
| |
| static BackwardFile parse(String backwardFile) throws IOException, ParseException { |
| BackwardFile backward = new BackwardFile(); |
| |
| List<String> lines = Files |
| .readAllLines(Paths.get(backwardFile), StandardCharsets.US_ASCII); |
| |
| // Remove comments |
| List<String> linkLines = |
| lines.stream() |
| .filter(s -> !(s.startsWith("#") || s.isEmpty())) |
| .collect(Collectors.toList()); |
| |
| for (String linkLine : linkLines) { |
| String[] fields = linkLine.split("\t+"); |
| if (fields.length < 3 || !fields[0].equals("Link")) { |
| throw new ParseException("Line is malformed: " + linkLine, 0); |
| } |
| backward.addLink(fields[1], fields[2]); |
| } |
| return backward; |
| } |
| |
| /** |
| * Add a link entry. |
| * |
| * @param target the new tz ID |
| * @param linkName the old tz ID |
| */ |
| private void addLink(String target, String linkName) { |
| String oldValue = links.put(linkName, target); |
| if (oldValue != null) { |
| throw new IllegalStateException("Duplicate link from " + linkName); |
| } |
| } |
| |
| /** Returns a mapping from linkName (old tz ID) to target (new tz ID). */ |
| Map<String, String> getDirectLinks() { |
| // Validate links for cycles and collapse the links if there are links to links. There's a |
| // simple check to confirm that no chain is longer than a fixed length, to guard against |
| // cycles. |
| final int maxChainLength = 2; |
| Map<String, String> collapsedLinks = new HashMap<>(); |
| for (String fromId : links.keySet()) { |
| int chainLength = 0; |
| String currentId = fromId; |
| String lastId = null; |
| while ((currentId = links.get(currentId)) != null) { |
| chainLength++; |
| lastId = currentId; |
| if (chainLength >= maxChainLength) { |
| throw new IllegalStateException( |
| "Chain from " + fromId + " is longer than " + maxChainLength); |
| } |
| } |
| if (chainLength == 0) { |
| throw new IllegalStateException("Null Link targetId for " + fromId); |
| } |
| collapsedLinks.put(fromId, lastId); |
| } |
| return Collections.unmodifiableMap(collapsedLinks); |
| } |
| } |