1/*
2 * Copyright (C) 2010 Google Inc.
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
17package com.google.doclava;
18
19import com.google.doclava.apicheck.ApiParseException;
20import java.net.URL;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.HashMap;
25import java.util.List;
26import java.util.Map;
27
28/**
29 * Cross-references documentation among different libraries. A FederationTagger
30 * is populated with a list of {@link FederatedSite} objects which are linked
31 * against when overlapping content is discovered.
32 */
33public final class FederationTagger {
34  private final Map<String, URL> federatedUrls = new HashMap<>();
35  private final Map<String, String> federatedXmls = new HashMap<>();
36  private final List<FederatedSite> federatedSites = new ArrayList<>();
37
38  private boolean initialized = false;
39
40  /**
41   * Adds a Doclava documentation site for federation. Accepts the base URL of
42   * the remote API.
43   * <p>
44   * If {@link #addSiteApi(String, String)} is not called, this will default to
45   * reading the API from "/xml/current.xml" within the site's base URL.
46   * <p>
47   * <strong>Note:</strong> Must be called before calling tag() or get() methods.
48   *
49   * @param name internally-used name for federation site
50   */
51  public void addSiteUrl(String name, URL site) {
52    if (initialized) {
53      throw new IllegalStateException("Cannot add sites after calling tag() or get() methods.");
54    }
55    federatedUrls.put(name, site);
56  }
57
58  /**
59   * Adds an explicit Doclava-generated API file for the specified site.
60   * <p>
61   * <strong>Note:</strong> Must be called before calling tag() or get() methods.
62   *
63   * @param name internally-used name for federation site (must match name used
64   *             for {@link #addSiteUrl(String, URL)})
65   * @param file path to a Doclava-generated API file
66   */
67  public void addSiteApi(String name, String file) {
68    if (initialized) {
69      throw new IllegalStateException("Cannot add sites after calling tag() or get() methods.");
70    }
71    federatedXmls.put(name, file);
72  }
73
74  public void tag(ClassInfo classDoc) {
75    initialize();
76    for (FederatedSite site : federatedSites) {
77      applyFederation(site, Arrays.asList(classDoc));
78    }
79  }
80
81  public void tagAll(Collection<ClassInfo> classDocs) {
82    initialize();
83    for (FederatedSite site : federatedSites) {
84      applyFederation(site, classDocs);
85    }
86  }
87
88  /**
89   * Returns a non-{@code null} list of {@link FederatedSite} objects, one for
90   * each unique {@code name} added using {@link #addSiteUrl(String, URL)}.
91   */
92  public List<FederatedSite> getSites() {
93    initialize();
94    return federatedSites;
95  }
96
97  private void initialize() {
98    if (initialized) {
99      return;
100    }
101
102    for (String name : federatedXmls.keySet()) {
103      if (!federatedUrls.containsKey(name)) {
104        Errors.error(Errors.NO_FEDERATION_DATA, (SourcePositionInfo) null,
105            "Unknown documentation site for " + name);
106      }
107    }
108
109    for (String name : federatedUrls.keySet()) {
110      try {
111        if (federatedXmls.containsKey(name)) {
112          federatedSites.add(new FederatedSite(name, federatedUrls.get(name),
113              federatedXmls.get(name)));
114        } else {
115          federatedSites.add(new FederatedSite(name, federatedUrls.get(name)));
116        }
117      } catch (ApiParseException e) {
118        String error = "Could not add site for federation: " + name;
119        if (e.getMessage() != null) {
120          error += ": " + e.getMessage();
121        }
122        Errors.error(Errors.NO_FEDERATION_DATA, (SourcePositionInfo) null, error);
123      }
124    }
125
126    initialized = true;
127  }
128
129  private void applyFederation(FederatedSite federationSource, Collection<ClassInfo> classDocs) {
130    for (ClassInfo classDoc : classDocs) {
131      PackageInfo packageSpec
132          = federationSource.apiInfo().getPackages().get(classDoc.containingPackage().name());
133
134      if (packageSpec == null) {
135        continue;
136      }
137
138      ClassInfo classSpec = packageSpec.allClasses().get(classDoc.name());
139
140      if (classSpec == null) {
141        continue;
142      }
143
144      federateMethods(federationSource, classSpec, classDoc);
145      federateConstructors(federationSource, classSpec, classDoc);
146      federateFields(federationSource, classSpec, classDoc);
147      federateClass(federationSource, classDoc);
148      federatePackage(federationSource, classDoc.containingPackage());
149    }
150  }
151
152  private void federateMethods(FederatedSite site, ClassInfo federatedClass, ClassInfo localClass) {
153    for (MethodInfo method : localClass.methods()) {
154      for (ClassInfo superclass : federatedClass.hierarchy()) {
155        if (superclass.allMethods().containsKey(method.getHashableName())) {
156          method.addFederatedReference(site);
157          break;
158        }
159      }
160    }
161  }
162
163  private void federateConstructors(FederatedSite site, ClassInfo federatedClass,
164      ClassInfo localClass) {
165    for (MethodInfo constructor : localClass.constructors()) {
166      if (federatedClass.hasConstructor(constructor)) {
167        constructor.addFederatedReference(site);
168      }
169    }
170  }
171
172  private void federateFields(FederatedSite site, ClassInfo federatedClass, ClassInfo localClass) {
173    for (FieldInfo field : localClass.fields()) {
174      if (federatedClass.allFields().containsKey(field.name())) {
175        field.addFederatedReference(site);
176      }
177    }
178  }
179
180  private void federateClass(FederatedSite source, ClassInfo doc) {
181    doc.addFederatedReference(source);
182  }
183
184  private void federatePackage(FederatedSite source, PackageInfo pkg) {
185    pkg.addFederatedReference(source);
186  }
187}
188