• 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.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