1 /* 2 * Copyright (C) 2017 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 17 package com.google.doclava; 18 19 import com.google.clearsilver.jsilver.data.Data; 20 import com.google.doclava.apicheck.ApiCheck; 21 import com.google.doclava.apicheck.ApiInfo; 22 import com.google.doclava.apicheck.ApiParseException; 23 24 import java.io.PrintWriter; 25 import java.io.StringWriter; 26 import java.util.Collection; 27 import java.util.LinkedHashMap; 28 import java.util.Map; 29 30 /** 31 * Applies version information to the Doclava class model from apicheck XML files. 32 * <p> 33 * Sample usage: 34 * <pre> 35 * ClassInfo[] classInfos = ... 36 * 37 * ArtifactTagger artifactTagger = new ArtifactTagger() 38 * artifactTagger.addArtifact("frameworks/support/core-ui/api/current.xml", 39 * "com.android.support:support-core-ui:26.0.0") 40 * artifactTagger.addArtifact("frameworks/support/design/api/current.xml", 41 * "com.android.support:support-design:26.0.0") 42 * artifactTagger.tagAll(...); 43 * </pre> 44 */ 45 public class ArtifactTagger { 46 47 private final Map<String, String> xmlToArtifact = new LinkedHashMap<>(); 48 49 /** 50 * Specifies the apicheck XML file associated with an artifact. 51 * <p> 52 * This method should only be called once per artifact. 53 * 54 * @param file an apicheck XML file 55 * @param mavenSpec the Maven spec for the artifact to which the XML file belongs 56 */ addArtifact(String file, String mavenSpec)57 public void addArtifact(String file, String mavenSpec) { 58 xmlToArtifact.put(file, mavenSpec); 59 } 60 61 /** 62 * Tags the specified docs with artifact information. 63 * 64 * @param classDocs the docs to tag 65 */ tagAll(Collection<ClassInfo> classDocs)66 public void tagAll(Collection<ClassInfo> classDocs) { 67 // Read through the XML files in order, applying their artifact information 68 // to the Javadoc models. 69 for (Map.Entry<String, String> artifactSpec : xmlToArtifact.entrySet()) { 70 String xmlFile = artifactSpec.getKey(); 71 String artifactName = artifactSpec.getValue(); 72 73 ApiInfo specApi; 74 try { 75 specApi = new ApiCheck().parseApi(xmlFile); 76 } catch (ApiParseException e) { 77 StringWriter stackTraceWriter = new StringWriter(); 78 e.printStackTrace(new PrintWriter(stackTraceWriter)); 79 Errors.error(Errors.BROKEN_ARTIFACT_FILE, (SourcePositionInfo) null, 80 "Failed to parse " + xmlFile + " for " + artifactName + " artifact data.\n" 81 + stackTraceWriter.toString()); 82 continue; 83 } 84 85 applyArtifactsFromSpec(artifactName, specApi, classDocs); 86 } 87 88 if (!xmlToArtifact.isEmpty()) { 89 warnForMissingArtifacts(classDocs); 90 } 91 } 92 93 /** 94 * Returns {@code true} if any artifact mappings are specified. 95 */ hasArtifacts()96 public boolean hasArtifacts() { 97 return !xmlToArtifact.isEmpty(); 98 } 99 100 /** 101 * Writes an index of the artifact names to {@code data}. 102 */ writeArtifactNames(Data data)103 public void writeArtifactNames(Data data) { 104 int index = 1; 105 for (String artifact : xmlToArtifact.values()) { 106 data.setValue("artifact." + index + ".name", artifact); 107 index++; 108 } 109 } 110 111 /** 112 * Applies artifact information to {@code classDocs} where not already present. 113 * 114 * @param mavenSpec the Maven spec for the artifact 115 * @param specApi the spec for this artifact 116 * @param classDocs the docs to update 117 */ applyArtifactsFromSpec(String mavenSpec, ApiInfo specApi, Collection<ClassInfo> classDocs)118 private void applyArtifactsFromSpec(String mavenSpec, ApiInfo specApi, 119 Collection<ClassInfo> classDocs) { 120 for (ClassInfo classDoc : classDocs) { 121 PackageInfo packageSpec = specApi.getPackages().get(classDoc.containingPackage().name()); 122 if (packageSpec != null) { 123 ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name()); 124 if (classSpec != null) { 125 if (classDoc.getArtifact() == null) { 126 classDoc.setArtifact(mavenSpec); 127 } else { 128 Errors.error(Errors.BROKEN_ARTIFACT_FILE, (SourcePositionInfo) null, "Class " 129 + classDoc.name() + " belongs to multiple artifacts: " + classDoc.getArtifact() 130 + " and " + mavenSpec); 131 } 132 } 133 } 134 } 135 } 136 137 /** 138 * Warns if any symbols are missing artifact information. When configured properly, this will 139 * yield zero warnings because {@code apicheck} guarantees that all symbols are present in the 140 * most recent API. 141 * 142 * @param classDocs the docs to verify 143 */ warnForMissingArtifacts(Collection<ClassInfo> classDocs)144 private void warnForMissingArtifacts(Collection<ClassInfo> classDocs) { 145 for (ClassInfo claz : classDocs) { 146 if (checkLevelRecursive(claz) && claz.getArtifact() == null) { 147 Errors.error(Errors.NO_ARTIFACT_DATA, claz.position(), "XML missing class " 148 + claz.qualifiedName()); 149 } 150 } 151 } 152 153 /** 154 * Returns true if {@code claz} and all containing classes are documented. The result may be used 155 * to filter out members that exist in the API data structure but aren't a part of the API. 156 */ checkLevelRecursive(ClassInfo claz)157 private boolean checkLevelRecursive(ClassInfo claz) { 158 for (ClassInfo c = claz; c != null; c = c.containingClass()) { 159 if (!c.checkLevel()) { 160 return false; 161 } 162 } 163 return true; 164 } 165 } 166