| 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.File; | 
| 14 | import java.io.FileFilter; | 
| 15 | import java.io.IOException; | 
| 16 | import java.util.ArrayList; | 
| 17 | import java.util.Collections; | 
| 18 | import java.util.List; | 
| 19 |   | 
| 20 | import javax.xml.parsers.ParserConfigurationException; | 
| 21 | import javax.xml.parsers.SAXParser; | 
| 22 | import javax.xml.parsers.SAXParserFactory; | 
| 23 |   | 
| 24 | import org.eclipse.core.runtime.IProgressMonitor; | 
| 25 | import org.eclipse.core.runtime.SubMonitor; | 
| 26 | import org.eclipse.osgi.util.NLS; | 
| 27 | import org.eclipse.pde.api.tools.internal.IApiXmlConstants; | 
| 28 | import org.eclipse.pde.api.tools.internal.provisional.Factory; | 
| 29 | import org.eclipse.pde.api.tools.internal.provisional.builder.IReference; | 
| 30 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IComponentDescriptor; | 
| 31 | import org.eclipse.pde.api.tools.internal.provisional.descriptors.IMemberDescriptor; | 
| 32 | import org.eclipse.pde.api.tools.internal.util.Util; | 
| 33 | import org.xml.sax.Attributes; | 
| 34 | import org.xml.sax.SAXException; | 
| 35 | import org.xml.sax.helpers.DefaultHandler; | 
| 36 |   | 
| 37 | /** | 
| 38 |  * Parses a use scan (XML) to visit a {@link UseScanVisitor} | 
| 39 |  */ | 
| 40 | public class UseScanParser { | 
| 41 |          | 
| 42 |         private UseScanVisitor visitor; | 
| 43 |          | 
| 44 |         private IComponentDescriptor targetComponent; | 
| 45 |         private IComponentDescriptor referencingComponent; | 
| 46 |         private IMemberDescriptor targetMember; | 
| 47 |         private int referenceKind; | 
| 48 |         private int visibility; | 
| 49 |          | 
| 50 |         private boolean visitReferencingComponent = true; | 
| 51 |         private boolean visitMembers = true; | 
| 52 |         private boolean visitReferences = true; | 
| 53 |   | 
| 54 |         /** | 
| 55 |          * Handler to resolve a reference | 
| 56 |          */ | 
| 57 |         class ReferenceHandler extends DefaultHandler { | 
| 58 |   | 
| 59 |                 // type of file being analyzed - type reference, method reference, field reference | 
| 60 |                 private int type = 0; | 
| 61 |   | 
| 62 |                 /** | 
| 63 |                  * Constructor | 
| 64 |                  *  | 
| 65 |                  * @param type one of IReference.T_TYPE_REFERENCE, IReference.T_METHOD_REFERENCE, | 
| 66 |                  *                         IReference.T_FIELD_REFERENCE | 
| 67 |                  */ | 
| 68 |                 public ReferenceHandler(int type) { | 
| 69 |                         this.type = type; | 
| 70 |                 } | 
| 71 |                  | 
| 72 |                 private String[] getIdVersion(String value) { | 
| 73 |                         int index = value.indexOf(' '); | 
| 74 |                         if (index > 0) { | 
| 75 |                                 String id = value.substring(0, index); | 
| 76 |                                 String version = value.substring(index + 1); | 
| 77 |                                 if (version.startsWith("(")) { //$NON-NLS-1$ | 
| 78 |                                         version = version.substring(1); | 
| 79 |                                         if (version.endsWith(")")) { //$NON-NLS-1$ | 
| 80 |                                                 version = version.substring(0,version.length() - 2); | 
| 81 |                                         } | 
| 82 |                                 } | 
| 83 |                                 return new String[]{id, version}; | 
| 84 |                         } | 
| 85 |                         return new String[]{value, null}; | 
| 86 |                 } | 
| 87 |                  | 
| 88 |                  | 
| 89 |                 /* (non-Javadoc) | 
| 90 |                  * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) | 
| 91 |                  */ | 
| 92 |                 public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { | 
| 93 |                         if (IApiXmlConstants.REFERENCES.equals(name)) { | 
| 94 |                                 String target = attributes.getValue(IApiXmlConstants.ATTR_REFEREE); | 
| 95 |                                 String source = attributes.getValue(IApiXmlConstants.ATTR_ORIGIN); | 
| 96 |                                 String[] idv = getIdVersion(target); | 
| 97 |                                 enterTargetComponent(Factory.componentDescriptor(idv[0], idv[1])); | 
| 98 |                                 idv = getIdVersion(source); | 
| 99 |                                 enterReferencingComponent(Factory.componentDescriptor(idv[0], idv[1])); | 
| 100 |                                 String visString = attributes.getValue(IApiXmlConstants.ATTR_REFERENCE_VISIBILITY); | 
| 101 |                                 try { | 
| 102 |                                         int vis = Integer.parseInt(visString); | 
| 103 |                                         enterVisibility(vis); | 
| 104 |                                 } catch (NumberFormatException e) { | 
| 105 |                                         // TODO: | 
| 106 |                                         enterVisibility(-1); | 
| 107 |                                         System.out.println("Internal error: invalid visibility: " + visString); //$NON-NLS-1$ | 
| 108 |                                 } | 
| 109 |                         } else if(IApiXmlConstants.ELEMENT_TARGET.equals(name)) { | 
| 110 |                                 String qName = attributes.getValue(IApiXmlConstants.ATTR_TYPE); | 
| 111 |                                 String memberName = attributes.getValue(IApiXmlConstants.ATTR_MEMBER_NAME); | 
| 112 |                                 String signature = attributes.getValue(IApiXmlConstants.ATTR_SIGNATURE); | 
| 113 |                                 IMemberDescriptor member = null; | 
| 114 |                                 switch (type) { | 
| 115 |                                         case IReference.T_TYPE_REFERENCE: | 
| 116 |                                                 member = Factory.typeDescriptor(qName); | 
| 117 |                                                 break; | 
| 118 |                                         case IReference.T_METHOD_REFERENCE: | 
| 119 |                                                 member = Factory.methodDescriptor(qName, memberName, signature); | 
| 120 |                                                 break; | 
| 121 |                                         case IReference.T_FIELD_REFERENCE: | 
| 122 |                                                 member = Factory.fieldDescriptor(qName, memberName); | 
| 123 |                                                 break; | 
| 124 |                                 } | 
| 125 |                                 enterTargetMember(member); | 
| 126 |                         } else if (IApiXmlConstants.REFERENCE_KIND.equals(name)) { | 
| 127 |                                 String value = attributes.getValue(IApiXmlConstants.ATTR_KIND); | 
| 128 |                                 if (value != null) { | 
| 129 |                                         try { | 
| 130 |                                                 enterReferenceKind(Integer.parseInt(value)); | 
| 131 |                                         } catch (NumberFormatException e) { | 
| 132 |                                                 // ERROR | 
| 133 |                                                 System.out.println(NLS.bind("Internal error: invalid reference kind: {0}", value)); //$NON-NLS-1$ | 
| 134 |                                         } | 
| 135 |                                 } | 
| 136 |                         } else if (IApiXmlConstants.ATTR_REFERENCE.equals(name)) { | 
| 137 |                                 String qName = attributes.getValue(IApiXmlConstants.ATTR_TYPE); | 
| 138 |                                 String memberName = attributes.getValue(IApiXmlConstants.ATTR_MEMBER_NAME); | 
| 139 |                                 String signature = attributes.getValue(IApiXmlConstants.ATTR_SIGNATURE); | 
| 140 |                                 IMemberDescriptor origin = null; | 
| 141 |                                 if (signature != null) { | 
| 142 |                                         origin = Factory.methodDescriptor(qName, memberName, signature); | 
| 143 |                                 } else if (memberName != null) { | 
| 144 |                                         origin = Factory.fieldDescriptor(qName, memberName); | 
| 145 |                                 } else { | 
| 146 |                                         origin = Factory.typeDescriptor(qName); | 
| 147 |                                 } | 
| 148 |                                 String line = attributes.getValue(IApiXmlConstants.ATTR_LINE_NUMBER); | 
| 149 |                                 try { | 
| 150 |                                         int num = Integer.parseInt(line); | 
| 151 |                                         setReference(origin, num); | 
| 152 |                                 } catch (NumberFormatException e) { | 
| 153 |                                         // TODO: | 
| 154 |                                         System.out.println("Internal error: invalid line number: " + line); //$NON-NLS-1$ | 
| 155 |                                 } | 
| 156 |                         } | 
| 157 |                 } | 
| 158 |         } | 
| 159 |          | 
| 160 |         /** | 
| 161 |          * Resolves references from an API use scan rooted at the specified location in the file | 
| 162 |          * system in the given baseline. | 
| 163 |          *  | 
| 164 |          * @param xmlLocation root of API use scan (XML directory). | 
| 165 |          * @param monitor progress monitor | 
| 166 |          * @param baseline API baseline to resolve references in | 
| 167 |          */ | 
| 168 |         public void parse(String xmlLocation, IProgressMonitor monitor, UseScanVisitor usv) throws Exception { | 
| 169 |                 if (xmlLocation == null) { | 
| 170 |                         throw new Exception(SearchMessages.missing_xml_files_location); | 
| 171 |                 } | 
| 172 |                 visitor = usv; | 
| 173 |                 File reportsRoot = new File(xmlLocation); | 
| 174 |                 if (!reportsRoot.exists() || !reportsRoot.isDirectory()) { | 
| 175 |                         throw new Exception(NLS.bind(SearchMessages.invalid_directory_name, xmlLocation)); | 
| 176 |                 } | 
| 177 |                 SubMonitor localmonitor = SubMonitor.convert(monitor, SearchMessages.UseScanParser_parsing, 8); | 
| 178 |                 localmonitor.setTaskName(SearchMessages.UseReportConverter_collecting_dir_info); | 
| 179 |                 File[] referees = getDirectories(reportsRoot); | 
| 180 |                 Util.updateMonitor(localmonitor, 1); | 
| 181 |                 File[] origins = null; | 
| 182 |                 File[] xmlfiles = null; | 
| 183 |                 SubMonitor smonitor = localmonitor.newChild(7); | 
| 184 |                 smonitor.setWorkRemaining(referees.length); | 
| 185 |                 visitor.visitScan(); | 
| 186 |                 try { | 
| 187 |                         SAXParser parser = getParser(); | 
| 188 |                         for (int i = 0; i < referees.length; i++) { | 
| 189 |                                 smonitor.setTaskName(NLS.bind(SearchMessages.UseScanParser_analyzing_references, new String[] {referees[i].getName()})); | 
| 190 |                                 origins = getDirectories(referees[i]); | 
| 191 |                                 origins = sort(origins); // sort to visit in determined order | 
| 192 |                                 for (int j = 0; j < origins.length; j++) { | 
| 193 |                                         xmlfiles = Util.getAllFiles(origins[j], new FileFilter() { | 
| 194 |                                                 public boolean accept(File pathname) { | 
| 195 |                                                         return pathname.isDirectory() || pathname.getName().endsWith(".xml"); //$NON-NLS-1$ | 
| 196 |                                                 } | 
| 197 |                                         }); | 
| 198 |                                         if (xmlfiles != null) { | 
| 199 |                                                 xmlfiles = sort(xmlfiles); // sort to visit in determined order | 
| 200 |                                                 for (int k = 0; k < xmlfiles.length; k++) { | 
| 201 |                                                         try { | 
| 202 |                                                                 ReferenceHandler handler = new ReferenceHandler(getTypeFromFileName(xmlfiles[k])); | 
| 203 |                                                                 parser.parse(xmlfiles[k], handler); | 
| 204 |                                                         }  | 
| 205 |                                                         catch (SAXException e) {} | 
| 206 |                                                         catch (IOException e) {} | 
| 207 |                                                 } | 
| 208 |                                         } | 
| 209 |                                 } | 
| 210 |                                 Util.updateMonitor(smonitor, 1); | 
| 211 |                         } | 
| 212 |                         endMember(); | 
| 213 |                         endReferencingComponent(); | 
| 214 |                         endTargetComponent(); | 
| 215 |                 } | 
| 216 |                 finally { | 
| 217 |                         visitor.endVisitScan(); | 
| 218 |                         if(!smonitor.isCanceled()) { | 
| 219 |                                 smonitor.done(); | 
| 220 |                         } | 
| 221 |                 }                 | 
| 222 |         } | 
| 223 |          | 
| 224 |         /** | 
| 225 |          * Returns a parser | 
| 226 |          * @return default parser | 
| 227 |          * @throws Exception forwarded general exception that can be trapped in Ant builds | 
| 228 |          */ | 
| 229 |         SAXParser getParser() throws Exception { | 
| 230 |                 SAXParserFactory factory = SAXParserFactory.newInstance(); | 
| 231 |                 try { | 
| 232 |                         return factory.newSAXParser(); | 
| 233 |                 } catch (ParserConfigurationException pce) { | 
| 234 |                         throw new Exception(SearchMessages.UseReportConverter_pce_error_getting_parser, pce); | 
| 235 |                 } catch (SAXException se) { | 
| 236 |                         throw new Exception(SearchMessages.UseReportConverter_se_error_parser_handle, se); | 
| 237 |                 } | 
| 238 |         }         | 
| 239 |          | 
| 240 |         /** | 
| 241 |          * Returns all the child directories form the given directory | 
| 242 |          * @param file | 
| 243 |          * @return | 
| 244 |          */ | 
| 245 |         private File[] getDirectories(File file) { | 
| 246 |                 File[] directories = file.listFiles(new FileFilter() { | 
| 247 |                         public boolean accept(File pathname) { | 
| 248 |                                 return pathname.isDirectory() && !pathname.isHidden(); | 
| 249 |                         } | 
| 250 |                 }); | 
| 251 |                 return directories; | 
| 252 |         }         | 
| 253 |          | 
| 254 |         /** | 
| 255 |          * Returns the {@link IReference} type from the file name | 
| 256 |          * @param xmlfile | 
| 257 |          * @return the type from the file name | 
| 258 |          */ | 
| 259 |         private int getTypeFromFileName(File xmlfile) { | 
| 260 |                 if(xmlfile.getName().indexOf(XmlReferenceDescriptorWriter.TYPE_REFERENCES) > -1) { | 
| 261 |                         return IReference.T_TYPE_REFERENCE; | 
| 262 |                 } | 
| 263 |                 if(xmlfile.getName().indexOf(XmlReferenceDescriptorWriter.METHOD_REFERENCES) > -1) { | 
| 264 |                         return IReference.T_METHOD_REFERENCE; | 
| 265 |                 } | 
| 266 |                 return IReference.T_FIELD_REFERENCE; | 
| 267 |         } | 
| 268 |          | 
| 269 |         public void enterTargetComponent(IComponentDescriptor component) { | 
| 270 |                 boolean different = false; | 
| 271 |                 if (targetComponent == null) { | 
| 272 |                         different = true; | 
| 273 |                 } else { | 
| 274 |                         if (!targetComponent.equals(component)) { | 
| 275 |                                 different = true; | 
| 276 |                         } | 
| 277 |                 } | 
| 278 |                 if (different) { | 
| 279 |                         // end visit | 
| 280 |                         endMember(); | 
| 281 |                         endReferencingComponent(); | 
| 282 |                         endTargetComponent(); | 
| 283 |                          | 
| 284 |                         // start next | 
| 285 |                         targetComponent = component; | 
| 286 |                         visitReferencingComponent = visitor.visitComponent(targetComponent); | 
| 287 |                 } | 
| 288 |         } | 
| 289 |          | 
| 290 |         public void enterReferencingComponent(IComponentDescriptor component) { | 
| 291 |                 boolean different = false; | 
| 292 |                 if (referencingComponent == null) { | 
| 293 |                         different = true; | 
| 294 |                 } else { | 
| 295 |                         if (!referencingComponent.equals(component)) { | 
| 296 |                                 different = true; | 
| 297 |                         } | 
| 298 |                 } | 
| 299 |                 if (different) { | 
| 300 |                         // end visit | 
| 301 |                         endMember(); | 
| 302 |                         endReferencingComponent(); | 
| 303 |                          | 
| 304 |                         // start next | 
| 305 |                         referencingComponent = component; | 
| 306 |                         if (visitReferencingComponent) { | 
| 307 |                                 visitMembers = visitor.visitReferencingComponent(referencingComponent); | 
| 308 |                         } | 
| 309 |                 }                 | 
| 310 |         } | 
| 311 |          | 
| 312 |         public void enterVisibility(int vis) { | 
| 313 |                 visibility = vis; | 
| 314 |         } | 
| 315 |          | 
| 316 |         public void enterTargetMember(IMemberDescriptor member) { | 
| 317 |                 if (targetMember == null || !targetMember.equals(member)) { | 
| 318 |                         endMember(); | 
| 319 |                         targetMember = member; | 
| 320 |                         if (visitReferencingComponent && visitMembers) { | 
| 321 |                                 visitReferences  =visitor.visitMember(targetMember); | 
| 322 |                         } | 
| 323 |                 } | 
| 324 |         } | 
| 325 |          | 
| 326 |         public void enterReferenceKind(int refKind) { | 
| 327 |                 referenceKind = refKind; | 
| 328 |         } | 
| 329 |          | 
| 330 |         public void setReference(IMemberDescriptor from, int lineNumber) { | 
| 331 |                 if (visitReferencingComponent&& visitMembers && visitReferences) { | 
| 332 |                         visitor.visitReference(referenceKind, from, lineNumber, visibility); | 
| 333 |                 } | 
| 334 |         } | 
| 335 |          | 
| 336 |         private void endMember() { | 
| 337 |                 if (targetMember != null) { | 
| 338 |                         visitor.endVisitMember(targetMember); | 
| 339 |                         targetMember = null; | 
| 340 |                 } | 
| 341 |         } | 
| 342 |          | 
| 343 |         private void endReferencingComponent() { | 
| 344 |                 if (referencingComponent != null) { | 
| 345 |                         visitor.endVisitReferencingComponent(referencingComponent); | 
| 346 |                         referencingComponent = null; | 
| 347 |                 } | 
| 348 |         } | 
| 349 |          | 
| 350 |         private void endTargetComponent() { | 
| 351 |                 if (targetComponent != null) { | 
| 352 |                         visitor.endVisit(targetComponent); | 
| 353 |                         targetComponent = null; | 
| 354 |                 } | 
| 355 |         } | 
| 356 |          | 
| 357 |         /** | 
| 358 |          * Sorts the given files by name (not path). | 
| 359 |          *  | 
| 360 |          * @param files | 
| 361 |          * @return sorted files | 
| 362 |          */ | 
| 363 |         private File[] sort(File[] files) { | 
| 364 |                 List sorted = new ArrayList(files.length + 2); | 
| 365 |                 for (int i = 0; i < files.length; i++) { | 
| 366 |                         sorted.add(files[i]); | 
| 367 |                 } | 
| 368 |                  | 
| 369 |                 Collections.sort(sorted, Util.filesorter); | 
| 370 |                 return (File[]) sorted.toArray(new File[sorted.size()]); | 
| 371 |         } | 
| 372 | } |