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.apicheck;
18
19import com.google.doclava.ClassInfo;
20import com.google.doclava.Errors;
21import com.google.doclava.PackageInfo;
22
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.HashMap;
27import java.util.List;
28import java.util.Map;
29
30public class ApiInfo {
31
32  private HashMap<String, PackageInfo> mPackages
33      = new HashMap<String, PackageInfo>();
34  private HashMap<String, ClassInfo> mAllClasses
35      = new HashMap<String, ClassInfo>();
36  private Map<ClassInfo,String> mClassToSuper
37      = new HashMap<ClassInfo, String>();
38  private Map<ClassInfo, ArrayList<String>> mClassToInterface
39      = new HashMap<ClassInfo, ArrayList<String>>();
40
41
42  public ClassInfo findClass(String name) {
43    return mAllClasses.get(name);
44  }
45
46  protected void resolveInterfaces() {
47    for (ClassInfo cl : mAllClasses.values()) {
48      ArrayList<String> ifaces = mClassToInterface.get(cl);
49      if (ifaces == null) {
50        continue;
51      }
52      for (String iface : ifaces) {
53        ClassInfo ci = mAllClasses.get(iface);
54        if (ci == null) {
55          // Interface not provided by this codebase. Inject a stub.
56          ci = new ClassInfo(iface);
57        }
58        cl.addInterface(ci);
59      }
60    }
61  }
62
63  /**
64   * Checks to see if this api is consistent with a newer version.
65   */
66  public boolean isConsistent(ApiInfo otherApi) {
67    return isConsistent(otherApi, null);
68  }
69
70  public boolean isConsistent(ApiInfo otherApi, List<PackageInfo> pkgInfoDiff) {
71      return isConsistent(otherApi, pkgInfoDiff, null, null);
72  }
73
74  /**
75   * Checks to see if this api is consistent with a newer version.
76   *
77   * @param otherApi the other api to test consistency against
78   * @param pkgInfoDiff
79   * @param ignoredPackages packages to skip consistency checks (will match by exact name)
80   * @param ignoredClasses classes to skip consistency checks (will match by exact fully qualified
81   * name)
82   */
83  public boolean isConsistent(ApiInfo otherApi, List<PackageInfo> pkgInfoDiff,
84      Collection<String> ignoredPackages, Collection<String> ignoredClasses) {
85    boolean consistent = true;
86    boolean diffMode = pkgInfoDiff != null;
87    for (PackageInfo pInfo : mPackages.values()) {
88      List<ClassInfo> newClsApis = null;
89
90      // TODO: Add support for matching subpackages (e.g, something like
91      // test.example.* should match test.example.subpackage, and
92      // test.example.** should match the above AND test.example.subpackage.more)
93      if (ignoredPackages != null && ignoredPackages.contains(pInfo.name())) {
94          // TODO: Log skipping this?
95          continue;
96      }
97      if (otherApi.getPackages().containsKey(pInfo.name())) {
98        if (diffMode) {
99          newClsApis = new ArrayList<>();
100        }
101        if (!pInfo.isConsistent(otherApi.getPackages().get(pInfo.name()), newClsApis, ignoredClasses)) {
102          consistent = false;
103        }
104        if (diffMode && !newClsApis.isEmpty()) {
105          PackageInfo info = new PackageInfo(pInfo.name(), pInfo.position());
106          for (ClassInfo cInfo : newClsApis) {
107            if (ignoredClasses == null || !ignoredClasses.contains(cInfo.qualifiedName())) {
108              info.addClass(cInfo);
109            }
110          }
111          pkgInfoDiff.add(info);
112        }
113      } else {
114        Errors.error(Errors.REMOVED_PACKAGE, pInfo.position(), "Removed package " + pInfo.name());
115        consistent = false;
116      }
117    }
118    for (PackageInfo pInfo : otherApi.mPackages.values()) {
119      if (ignoredPackages != null && ignoredPackages.contains(pInfo.name())) {
120          // TODO: Log skipping this?
121          continue;
122      }
123      if (!mPackages.containsKey(pInfo.name())) {
124        Errors.error(Errors.ADDED_PACKAGE, pInfo.position(), "Added package " + pInfo.name());
125        consistent = false;
126        if (diffMode) {
127          pkgInfoDiff.add(pInfo);
128        }
129      }
130    }
131    if (diffMode) {
132      Collections.sort(pkgInfoDiff, PackageInfo.comparator);
133    }
134    return consistent;
135  }
136
137  public HashMap<String, PackageInfo> getPackages() {
138    return mPackages;
139  }
140
141  protected void mapClassToSuper(ClassInfo classInfo, String superclass) {
142    mClassToSuper.put(classInfo, superclass);
143  }
144
145  protected void mapClassToInterface(ClassInfo classInfo, String iface) {
146    if (!mClassToInterface.containsKey(classInfo)) {
147      mClassToInterface.put(classInfo, new ArrayList<String>());
148    }
149    mClassToInterface.get(classInfo).add(iface);
150  }
151
152  protected void addPackage(PackageInfo pInfo) {
153    // track the set of organized packages in the API
154    pInfo.setContainingApi(this);
155    mPackages.put(pInfo.name(), pInfo);
156
157    // accumulate a direct map of all the classes in the API
158    for (ClassInfo cl : pInfo.allClasses().values()) {
159      mAllClasses.put(cl.qualifiedName(), cl);
160    }
161  }
162
163  protected void resolveSuperclasses() {
164    for (ClassInfo cl : mAllClasses.values()) {
165      // java.lang.Object has no superclass
166      if (!cl.qualifiedName().equals("java.lang.Object")) {
167        String scName = mClassToSuper.get(cl);
168        if (scName == null) {
169          scName = "java.lang.Object";
170        }
171        ClassInfo superclass = mAllClasses.get(scName);
172        if (superclass == null) {
173          // Superclass not provided by this codebase. Inject a stub.
174          superclass = new ClassInfo(scName);
175        }
176        cl.setSuperClass(superclass);
177      }
178    }
179  }
180}
181