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