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