• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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