| 1 | /******************************************************************************* | 
| 2 |  * Copyright (c) 2009 IBM Corporation and others. | 
| 3 |  * All rights reserved. This program and the accompanying materials | 
| 4 |  * are made available under the terms of the Eclipse Public License v1.0 | 
| 5 |  * which accompanies this distribution, and is available at | 
| 6 |  * http://www.eclipse.org/legal/epl-v10.html | 
| 7 |  * | 
| 8 |  * Contributors: | 
| 9 |  *     IBM Corporation - initial API and implementation | 
| 10 |  *******************************************************************************/ | 
| 11 | package org.eclipse.pde.api.tools.internal.search; | 
| 12 |   | 
| 13 | import java.io.BufferedWriter; | 
| 14 | import java.io.File; | 
| 15 | import java.io.FileNotFoundException; | 
| 16 | import java.io.FileWriter; | 
| 17 | import java.io.IOException; | 
| 18 | import java.util.ArrayList; | 
| 19 | import java.util.Iterator; | 
| 20 | import java.util.List; | 
| 21 |   | 
| 22 | import org.eclipse.core.runtime.CoreException; | 
| 23 | import org.eclipse.pde.api.tools.internal.IApiXmlConstants; | 
| 24 | import org.eclipse.pde.api.tools.internal.builder.Reference; | 
| 25 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; | 
| 26 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IComponentDescriptor; | 
| 27 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor; | 
| 28 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMemberDescriptor; | 
| 29 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMethodDescriptor; | 
| 30 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IReferenceTypeDescriptor; | 
| 31 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiBaseline; | 
| 32 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent; | 
| 33 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiMember; | 
| 34 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiType; | 
| 35 | import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot; | 
| 36 | import org.eclipse.pde.api.tools.internal.util.Util; | 
| 37 | import org.w3c.dom.Document; | 
| 38 | import org.w3c.dom.Element; | 
| 39 |   | 
| 40 | /** | 
| 41 |  * Resolves references from an API use scan in an alternate baseline to see if the | 
| 42 |  * reference still exists in that baseline. Can be used to detect potential migration | 
| 43 |  * issues.  | 
| 44 |  */ | 
| 45 | public class ReferenceLookupVisitor extends UseScanVisitor { | 
| 46 |          | 
| 47 |         private IApiBaseline baseline; // baseline to resolve in | 
| 48 |         private IComponentDescriptor targetComponent; // references are made to this component | 
| 49 |         private IComponentDescriptor referencingComponent; // references are made from this component | 
| 50 |         private IApiComponent currComponent; // corresponding component in baseline | 
| 51 |         private boolean skipped = false; // whether the target component was skipped based on scope settings | 
| 52 |         private IMemberDescriptor targetMember; // member a reference has been made to | 
| 53 |         private IReferenceTypeDescriptor targetType; // the enclosing type the reference has been made to | 
| 54 |         private IApiType currType; // corresponding type for current member | 
| 55 |          | 
| 56 |         private List missingComponents = new ArrayList(); // list of missing component descriptors | 
| 57 |         private List skippedComponents = new ArrayList(); // list of skipped component descriptors | 
| 58 |          | 
| 59 |         private String location; // path in file system to create report in | 
| 60 |          | 
| 61 |         private List unresolved = null; // list of reference descriptors (errors) | 
| 62 |          | 
| 63 |         private String analysisScope = null; // the bundles to analyze references from (search scope) | 
| 64 |         private String targetScope = null; // the bundles to analyze references to (target scope) | 
| 65 |          | 
| 66 |         /** | 
| 67 |          * Creates a visitor to resolve references in the given baseline | 
| 68 |          *  | 
| 69 |          * @param base baseline | 
| 70 |          * @param location to create XML report | 
| 71 |          */ | 
| 72 |         public ReferenceLookupVisitor(IApiBaseline base, String xmlLocation) { | 
| 73 |                 baseline = base; | 
| 74 |                 location = xmlLocation; | 
| 75 |         } | 
| 76 |          | 
| 77 |         /* (non-Javadoc) | 
| 78 |          * @see org.eclipse.pde.api.tools.internal.search.UseScanVisitor#visitComponent(org.eclipse.pde.api.tools.internal.provisional.descriptors.IComponentDescriptor) | 
| 79 |          */ | 
| 80 |         public boolean visitComponent(IComponentDescriptor target) { | 
| 81 |                 unresolved = new ArrayList(); | 
| 82 |                 targetComponent = target; | 
| 83 |                 skipped = false; | 
| 84 |                 if (targetScope == null || target.getId().matches(targetScope)) { | 
| 85 |                         // only analyze if it matches our scope | 
| 86 |                         currComponent = baseline.getApiComponent(targetComponent.getId()); | 
| 87 |                         return true;                         | 
| 88 |                 } | 
| 89 |                 skipped = true; | 
| 90 |                 return false; | 
| 91 |         } | 
| 92 |          | 
| 93 |         /* (non-Javadoc) | 
| 94 |          * @see org.eclipse.pde.api.tools.internal.search.UseScanVisitor#visitReferencingComponent(org.eclipse.pde.api.tools.internal.provisional.descriptors.IComponentDescriptor) | 
| 95 |          */ | 
| 96 |         public boolean visitReferencingComponent(IComponentDescriptor component) { | 
| 97 |                 referencingComponent = component; | 
| 98 |                 if (currComponent == null) { | 
| 99 |                         return false; | 
| 100 |                 } | 
| 101 |                 if (analysisScope == null || component.getId().matches(analysisScope)) { | 
| 102 |                         // only consider if in scope | 
| 103 |                         return true; | 
| 104 |                 } | 
| 105 |                 return false; | 
| 106 |         } | 
| 107 |          | 
| 108 |         /* (non-Javadoc) | 
| 109 |          * @see org.eclipse.pde.api.tools.internal.search.UseScanVisitor#visitMember(org.eclipse.pde.api.tools.internal.provisional.descriptors.IMemberDescriptor) | 
| 110 |          */ | 
| 111 |         public boolean visitMember(IMemberDescriptor referencedMember) { | 
| 112 |                 targetMember = referencedMember; | 
| 113 |                 switch (targetMember.getElementType()) { | 
| 114 |                 case IElementDescriptor.TYPE: | 
| 115 |                         targetType = (IReferenceTypeDescriptor)targetMember; | 
| 116 |                         break; | 
| 117 |                 case IElementDescriptor.METHOD: | 
| 118 |                 case IElementDescriptor.FIELD: | 
| 119 |                         targetType = targetMember.getEnclosingType(); | 
| 120 |                         break; | 
| 121 |                 } | 
| 122 |                 currType = null; | 
| 123 |                 try { | 
| 124 |                         IApiTypeRoot typeRoot = currComponent.findTypeRoot(targetType.getQualifiedName()); | 
| 125 |                         if (typeRoot != null) { | 
| 126 |                                 currType = typeRoot.getStructure(); | 
| 127 |                         } | 
| 128 |                         return true; | 
| 129 |                 } catch (CoreException e) { | 
| 130 |                         ApiPlugin.log(e.getStatus()); | 
| 131 |                 } | 
| 132 |                 return false; | 
| 133 |         } | 
| 134 |          | 
| 135 |         /* (non-Javadoc) | 
| 136 |          * @see org.eclipse.pde.api.tools.internal.search.UseScanVisitor#visitReference(int, java.lang.String, int, int) | 
| 137 |          */ | 
| 138 |         public void visitReference(int refKind, IMemberDescriptor origin, int lineNumber, int visibility) { | 
| 139 |                 Reference ref = null; | 
| 140 |                 IApiMember resolved = null; | 
| 141 |                 if (currType != null) { | 
| 142 |                         switch (targetMember.getElementType()) { | 
| 143 |                         case IElementDescriptor.TYPE: | 
| 144 |                                 ref = Reference.typeReference(currType, targetType.getQualifiedName(), refKind); | 
| 145 |                                 break; | 
| 146 |                         case IElementDescriptor.METHOD: | 
| 147 |                                 ref = Reference.methodReference(currType, targetType.getQualifiedName(), targetMember.getName(), ((IMethodDescriptor)targetMember).getSignature(), refKind); | 
| 148 |                                 break; | 
| 149 |                         case IElementDescriptor.FIELD: | 
| 150 |                                 ref = Reference.fieldReference(currType, targetType.getQualifiedName(), targetMember.getName(), refKind); | 
| 151 |                                 break; | 
| 152 |                         } | 
| 153 |                 } | 
| 154 |                 if (ref != null) { | 
| 155 |                         try { | 
| 156 |                                 ref.resolve(); | 
| 157 |                                 resolved = ref.getResolvedReference(); | 
| 158 |                         } catch (CoreException e) { | 
| 159 |                                 ApiPlugin.log(e.getStatus()); | 
| 160 |                         } | 
| 161 |                 } | 
| 162 |                 if (resolved == null) { | 
| 163 |                         // ERROR - failed to resolve | 
| 164 |                         addError(new ReferenceDescriptor(referencingComponent, origin, lineNumber, targetComponent, targetMember, refKind, visibility)); | 
| 165 |                 } | 
| 166 |         } | 
| 167 |          | 
| 168 |         private void addError(IReferenceDescriptor error) { | 
| 169 |                 unresolved.add(error); | 
| 170 |         } | 
| 171 |          | 
| 172 |         /* (non-Javadoc) | 
| 173 |          * @see org.eclipse.pde.api.tools.internal.search.UseScanVisitor#endVisit(org.eclipse.pde.api.tools.internal.provisional.descriptors.IComponentDescriptor) | 
| 174 |          */ | 
| 175 |         public void endVisit(IComponentDescriptor target) { | 
| 176 |                 if (skipped) { | 
| 177 |                         skippedComponents.add(target); | 
| 178 |                 } else { | 
| 179 |                         if (currComponent == null) { | 
| 180 |                                 missingComponents.add(target); | 
| 181 |                         } else { | 
| 182 |                                 if (!unresolved.isEmpty()) { | 
| 183 |                                         XmlReferenceDescriptorWriter writer = new XmlReferenceDescriptorWriter(location); | 
| 184 |                                         writer.setAlternate((IComponentDescriptor) currComponent.getHandle()); | 
| 185 |                                         writer.writeReferences((IReferenceDescriptor[]) unresolved.toArray(new IReferenceDescriptor[unresolved.size()])); | 
| 186 |                                 } | 
| 187 |                         } | 
| 188 |                 } | 
| 189 |         } | 
| 190 |          | 
| 191 |         /* (non-Javadoc) | 
| 192 |          * @see org.eclipse.pde.api.tools.internal.search.UseScanVisitor#endVisitScan() | 
| 193 |          */ | 
| 194 |         public void endVisitScan() { | 
| 195 |                 BufferedWriter writer = null; | 
| 196 |                 try { | 
| 197 |                         // generate missing bundles information | 
| 198 |                         File rootfile = new File(location); | 
| 199 |                         if(!rootfile.exists()) { | 
| 200 |                                 rootfile.mkdirs(); | 
| 201 |                         } | 
| 202 |                         File file = new File(rootfile, "not_searched.xml"); //$NON-NLS-1$ | 
| 203 |                         if(!file.exists()) { | 
| 204 |                                 file.createNewFile(); | 
| 205 |                         } | 
| 206 |                         Document doc = Util.newDocument(); | 
| 207 |                         Element root = doc.createElement(IApiXmlConstants.ELEMENT_COMPONENTS); | 
| 208 |                         doc.appendChild(root); | 
| 209 |                         addMissingComponents(missingComponents, SearchMessages.ReferenceLookupVisitor_0, doc, root); | 
| 210 |                         addMissingComponents(skippedComponents, SearchMessages.SkippedComponent_component_was_excluded, doc, root); | 
| 211 |                         writer = new BufferedWriter(new FileWriter(file)); | 
| 212 |                         writer.write(Util.serializeDocument(doc)); | 
| 213 |                         writer.flush(); | 
| 214 |                 } | 
| 215 |                 catch(FileNotFoundException fnfe) {} | 
| 216 |                 catch(IOException ioe) {} | 
| 217 |                 catch(CoreException ce) {} | 
| 218 |                 finally { | 
| 219 |                         try { | 
| 220 |                                 if(writer != null) { | 
| 221 |                                         writer.close(); | 
| 222 |                                 } | 
| 223 |                         }  | 
| 224 |                         catch (IOException e) {} | 
| 225 |                 } | 
| 226 |         } | 
| 227 |          | 
| 228 |         private void addMissingComponents(List missing, String details, Document doc, Element root) { | 
| 229 |                 Iterator iter = missing.iterator(); | 
| 230 |                 while (iter.hasNext()) { | 
| 231 |                         IComponentDescriptor component = (IComponentDescriptor)iter.next(); | 
| 232 |                         Element comp = doc.createElement(IApiXmlConstants.ELEMENT_COMPONENT); | 
| 233 |                         comp.setAttribute(IApiXmlConstants.ATTR_ID, component.getId()); | 
| 234 |                         comp.setAttribute(IApiXmlConstants.ATTR_VERSION, component.getVersion()); | 
| 235 |                         comp.setAttribute(IApiXmlConstants.SKIPPED_DETAILS, details); | 
| 236 |                         root.appendChild(comp); | 
| 237 |                 } | 
| 238 |         } | 
| 239 |          | 
| 240 |         /** | 
| 241 |          * Limits the scope of bundles to consider references from, as a regular expression. | 
| 242 |          *  | 
| 243 |          * @param regex regular expression or <code>null</code> if all | 
| 244 |          */ | 
| 245 |         public void setAnalysisScope(String regex) { | 
| 246 |                 analysisScope = regex; | 
| 247 |         } | 
| 248 |          | 
| 249 |         /** | 
| 250 |          * Limits the set of bundles to consider analyzing references to, as a regular expression. | 
| 251 |          *   | 
| 252 |          * @param regex regular expression or <code>null</code> if all. | 
| 253 |          */ | 
| 254 |         public void setTargetScope(String regex) { | 
| 255 |                 targetScope = regex; | 
| 256 |         } | 
| 257 |   | 
| 258 | } |