1 | /******************************************************************************* |
2 | * Copyright (c) 2008, 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; |
12 | |
13 | import java.io.IOException; |
14 | import java.io.InputStream; |
15 | import java.util.ArrayList; |
16 | import java.util.Collection; |
17 | import java.util.Collections; |
18 | import java.util.Comparator; |
19 | import java.util.HashMap; |
20 | import java.util.HashSet; |
21 | import java.util.Iterator; |
22 | import java.util.List; |
23 | import java.util.Map; |
24 | import java.util.Set; |
25 | |
26 | import org.eclipse.core.resources.IFile; |
27 | import org.eclipse.core.resources.IFolder; |
28 | import org.eclipse.core.resources.IProject; |
29 | import org.eclipse.core.resources.IResource; |
30 | import org.eclipse.core.resources.IResourceChangeEvent; |
31 | import org.eclipse.core.resources.IResourceChangeListener; |
32 | import org.eclipse.core.resources.IResourceDelta; |
33 | import org.eclipse.core.resources.IncrementalProjectBuilder; |
34 | import org.eclipse.core.resources.ResourcesPlugin; |
35 | import org.eclipse.core.resources.WorkspaceJob; |
36 | import org.eclipse.core.runtime.Assert; |
37 | import org.eclipse.core.runtime.CoreException; |
38 | import org.eclipse.core.runtime.IPath; |
39 | import org.eclipse.core.runtime.IProgressMonitor; |
40 | import org.eclipse.core.runtime.IStatus; |
41 | import org.eclipse.core.runtime.Path; |
42 | import org.eclipse.core.runtime.Status; |
43 | import org.eclipse.core.runtime.SubMonitor; |
44 | import org.eclipse.core.runtime.jobs.Job; |
45 | import org.eclipse.jdt.core.IJavaProject; |
46 | import org.eclipse.pde.api.tools.internal.problems.ApiProblemFactory; |
47 | import org.eclipse.pde.api.tools.internal.problems.ApiProblemFilter; |
48 | import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin; |
49 | import org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore; |
50 | import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem; |
51 | import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblemFilter; |
52 | import org.eclipse.pde.api.tools.internal.util.Util; |
53 | import org.w3c.dom.Document; |
54 | import org.w3c.dom.Element; |
55 | import org.w3c.dom.NodeList; |
56 | |
57 | /** |
58 | * Base implementation of a filter store for Api components |
59 | * |
60 | * @since 1.0.0 |
61 | */ |
62 | public class ApiFilterStore implements IApiFilterStore, IResourceChangeListener { |
63 | /** |
64 | * Constant representing the name of the .settings folder |
65 | */ |
66 | private static final String SETTINGS_FOLDER = ".settings"; //$NON-NLS-1$ |
67 | public static final String GLOBAL = "!global!"; //$NON-NLS-1$ |
68 | public static final int CURRENT_STORE_VERSION = 2; |
69 | /** |
70 | * Constant used for controlling tracing in the plug-in workspace component |
71 | */ |
72 | static boolean DEBUG = Util.DEBUG; |
73 | |
74 | /** |
75 | * Method used for initializing tracing in the plug-in workspace component |
76 | */ |
77 | public static void setDebug(boolean debugValue) { |
78 | DEBUG = debugValue || Util.DEBUG; |
79 | } |
80 | /** |
81 | * Represents no filters |
82 | */ |
83 | private static IApiProblemFilter[] NO_FILTERS = new IApiProblemFilter[0]; |
84 | |
85 | /** |
86 | * The mapping of filters for this store. |
87 | * <pre> |
88 | * HashMap<IResource, HashSet<IApiProblemFilter>> |
89 | * </pre> |
90 | */ |
91 | private HashMap fFilterMap = null; |
92 | |
93 | /** |
94 | * Map used to collect unused {@link IApiProblemFilter}s |
95 | */ |
96 | private HashMap fUnusedFilters = null; |
97 | |
98 | /** |
99 | * The backing {@link IJavaProject} |
100 | */ |
101 | IJavaProject fProject = null; |
102 | |
103 | boolean fNeedsSaving = false; |
104 | boolean fTriggeredChange = false; |
105 | |
106 | /** |
107 | * Constructor |
108 | * @param owningComponent the id of the component that owns this filter store |
109 | */ |
110 | public ApiFilterStore(IJavaProject project) { |
111 | Assert.isNotNull(project); |
112 | fProject = project; |
113 | ResourcesPlugin.getWorkspace().addResourceChangeListener(this); |
114 | } |
115 | |
116 | /** |
117 | * Saves the .api_filters file for the component |
118 | * @throws IOException |
119 | */ |
120 | private void persistApiFilters() { |
121 | if(!fNeedsSaving) { |
122 | return; |
123 | } |
124 | WorkspaceJob job = new WorkspaceJob(Util.EMPTY_STRING) { |
125 | public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { |
126 | if(DEBUG) { |
127 | System.out.println("persisting api filters for plugin project component ["+fProject.getElementName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
128 | } |
129 | try { |
130 | SubMonitor localmonitor = SubMonitor.convert(monitor); |
131 | IProject project = fProject.getProject(); |
132 | if(!project.isAccessible()) { |
133 | if(DEBUG) { |
134 | System.out.println("project ["+fProject.getElementName()+"] is not accessible, saving termainated"); //$NON-NLS-1$ //$NON-NLS-2$ |
135 | } |
136 | return Status.CANCEL_STATUS; |
137 | } |
138 | String xml = getStoreAsXml(); |
139 | IFile file = project.getFile(getFilterFilePath(false)); |
140 | if(xml == null) { |
141 | // no filters - delete the file if it exists |
142 | if (file.isAccessible()) { |
143 | IFolder folder = (IFolder) file.getParent(); |
144 | file.delete(true, localmonitor); |
145 | if(folder.members().length == 0 && folder.isAccessible()) { |
146 | folder.delete(true, localmonitor); |
147 | } |
148 | fTriggeredChange = true; |
149 | } |
150 | return Status.OK_STATUS; |
151 | } |
152 | InputStream xstream = Util.getInputStreamFromString(xml); |
153 | if(xstream == null) { |
154 | return Status.CANCEL_STATUS; |
155 | } |
156 | try { |
157 | if(!file.exists()) { |
158 | IFolder folder = (IFolder) file.getParent(); |
159 | if(!folder.exists()) { |
160 | folder.create(true, true, localmonitor); |
161 | } |
162 | file.create(xstream, true, localmonitor); |
163 | } |
164 | else { |
165 | file.setContents(xstream, true, false, localmonitor); |
166 | } |
167 | } |
168 | finally { |
169 | xstream.close(); |
170 | } |
171 | fTriggeredChange = true; |
172 | fNeedsSaving = false; |
173 | } |
174 | catch(CoreException ce) { |
175 | ApiPlugin.log(ce); |
176 | } |
177 | catch (IOException ioe) { |
178 | ApiPlugin.log(ioe); |
179 | } |
180 | return Status.OK_STATUS; |
181 | } |
182 | }; |
183 | job.setSystem(true); |
184 | job.setPriority(Job.INTERACTIVE); |
185 | job.schedule(); |
186 | } |
187 | |
188 | /* (non-Javadoc) |
189 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore#addFilters(org.eclipse.pde.api.tools.internal.provisional.IApiProblemFilter[]) |
190 | */ |
191 | public synchronized void addFilters(IApiProblemFilter[] filters) { |
192 | if(filters == null) { |
193 | if(DEBUG) { |
194 | System.out.println("null filters array, not adding filters"); //$NON-NLS-1$ |
195 | } |
196 | return; |
197 | } |
198 | initializeApiFilters(); |
199 | for(int i = 0; i < filters.length; i++) { |
200 | IApiProblem problem = filters[i].getUnderlyingProblem(); |
201 | String resourcePath = problem.getResourcePath(); |
202 | if (resourcePath == null) { |
203 | continue; |
204 | } |
205 | IResource resource = fProject.getProject().findMember(new Path(resourcePath)); |
206 | if(resource == null) { |
207 | continue; |
208 | } |
209 | Map pTypeNames = (HashMap) fFilterMap.get(resource); |
210 | String typeName = problem.getTypeName(); |
211 | if (typeName == null) { |
212 | typeName = GLOBAL; |
213 | } |
214 | Set pfilters = null; |
215 | if(pTypeNames == null) { |
216 | pTypeNames = new HashMap(); |
217 | pfilters = new HashSet(); |
218 | pTypeNames.put(typeName, pfilters); |
219 | fFilterMap.put(resource, pTypeNames); |
220 | } else { |
221 | pfilters = (Set) pTypeNames.get(typeName); |
222 | if (pfilters == null) { |
223 | pfilters = new HashSet(); |
224 | pTypeNames.put(typeName, pfilters); |
225 | } |
226 | } |
227 | fNeedsSaving |= pfilters.add(filters[i]); |
228 | } |
229 | persistApiFilters(); |
230 | } |
231 | |
232 | /* (non-Javadoc) |
233 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore#addFilters(org.eclipse.pde.api.tools.internal.provisional.IApiProblem[]) |
234 | */ |
235 | public synchronized void addFiltersFor(IApiProblem[] problems) { |
236 | if(problems == null) { |
237 | if(DEBUG) { |
238 | System.out.println("null problems array: not addding filters"); //$NON-NLS-1$ |
239 | } |
240 | return; |
241 | } |
242 | if(problems.length < 1) { |
243 | if(DEBUG) { |
244 | System.out.println("empty problem array: not addding filters"); //$NON-NLS-1$ |
245 | } |
246 | return; |
247 | } |
248 | initializeApiFilters(); |
249 | internalAddFilters(problems, true); |
250 | } |
251 | |
252 | /* (non-Javadoc) |
253 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore#getFilters(org.eclipse.core.resources.IResource) |
254 | */ |
255 | public synchronized IApiProblemFilter[] getFilters(IResource resource) { |
256 | initializeApiFilters(); |
257 | Map pTypeNames = (Map) fFilterMap.get(resource); |
258 | if(pTypeNames == null) { |
259 | return NO_FILTERS; |
260 | } |
261 | List allFilters = new ArrayList(); |
262 | for (Iterator iterator = pTypeNames.values().iterator(); iterator.hasNext(); ) { |
263 | Set values = (Set) iterator.next(); |
264 | allFilters.addAll(values); |
265 | } |
266 | return (IApiProblemFilter[]) allFilters.toArray(new IApiProblemFilter[allFilters.size()]); |
267 | } |
268 | |
269 | /* (non-Javadoc) |
270 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore#isFiltered(org.eclipse.pde.api.tools.internal.provisional.IApiProblem) |
271 | */ |
272 | public synchronized boolean isFiltered(IApiProblem problem) { |
273 | initializeApiFilters(); |
274 | String resourcePath = problem.getResourcePath(); |
275 | if (resourcePath == null) { |
276 | return false; |
277 | } |
278 | IResource resource = fProject.getProject().findMember(new Path(resourcePath)); |
279 | if(resource == null) { |
280 | if(DEBUG) { |
281 | System.out.println("no resource exists: ["+resourcePath+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
282 | } |
283 | return false; |
284 | } |
285 | IApiProblemFilter[] filters = this.getFilters(resource); |
286 | if(filters == null) { |
287 | if(DEBUG) { |
288 | System.out.println("no filters defined for ["+resourcePath+"] return not filtered"); //$NON-NLS-1$ //$NON-NLS-2$ |
289 | } |
290 | return false; |
291 | } |
292 | IApiProblemFilter filter = null; |
293 | for(int i = 0, max = filters.length; i < max; i++) { |
294 | filter = filters[i]; |
295 | if(filter.getUnderlyingProblem().equals(problem)) { |
296 | if(DEBUG) { |
297 | System.out.println("recording filter used: ["+filter.toString()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
298 | } |
299 | recordFilterUsed(resource, filter); |
300 | return true; |
301 | } |
302 | } |
303 | if(DEBUG) { |
304 | System.out.println("no filter defined for problem: ["+problem.toString()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
305 | } |
306 | return false; |
307 | } |
308 | |
309 | /* (non-Javadoc) |
310 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore#dispose() |
311 | */ |
312 | public void dispose() { |
313 | clearFilters(); |
314 | if(fUnusedFilters != null) { |
315 | fUnusedFilters.clear(); |
316 | fUnusedFilters = null; |
317 | } |
318 | ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); |
319 | } |
320 | |
321 | /* (non-Javadoc) |
322 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore#getResources() |
323 | */ |
324 | public synchronized IResource[] getResources() { |
325 | initializeApiFilters(); |
326 | Collection resources = fFilterMap.keySet(); |
327 | return (IResource[]) resources.toArray(new IResource[resources.size()]); |
328 | } |
329 | |
330 | /* (non-Javadoc) |
331 | * @see org.eclipse.pde.api.tools.internal.provisional.IApiFilterStore#removeFilters(org.eclipse.pde.api.tools.internal.provisional.IApiProblemFilter[]) |
332 | */ |
333 | public synchronized boolean removeFilters(IApiProblemFilter[] filters) { |
334 | if(filters == null) { |
335 | if(DEBUG) { |
336 | System.out.println("null filters array, not removing"); //$NON-NLS-1$ |
337 | } |
338 | return false; |
339 | } |
340 | if(fFilterMap == null) { |
341 | if(DEBUG) { |
342 | System.out.println("null filter map, not removing"); //$NON-NLS-1$ |
343 | } |
344 | return false; |
345 | } |
346 | boolean success = true; |
347 | for(int i = 0; i < filters.length; i++) { |
348 | IApiProblem underlyingProblem = filters[i].getUnderlyingProblem(); |
349 | String resourcePath = underlyingProblem.getResourcePath(); |
350 | if (resourcePath == null) { |
351 | continue; |
352 | } |
353 | IResource resource = fProject.getProject().findMember(new Path(resourcePath)); |
354 | if(resource == null) { |
355 | resource = fProject.getProject().getFile(resourcePath); |
356 | } |
357 | Map pTypeNames = (Map) fFilterMap.get(resource); |
358 | if (pTypeNames == null) { |
359 | continue; |
360 | } |
361 | String typeName = underlyingProblem.getTypeName(); |
362 | if (typeName == null) { |
363 | typeName = GLOBAL; |
364 | } |
365 | Set pfilters = (Set) pTypeNames.get(typeName); |
366 | if(pfilters != null && pfilters.remove(filters[i])) { |
367 | if(DEBUG) { |
368 | System.out.println("removed filter: ["+filters[i]+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
369 | } |
370 | fNeedsSaving |= true; |
371 | success &= true; |
372 | if(pfilters.isEmpty()) { |
373 | pTypeNames.remove(typeName); |
374 | if (pTypeNames.isEmpty()) { |
375 | success &= fFilterMap.remove(resource) != null; |
376 | } |
377 | } |
378 | } else { |
379 | success &= false; |
380 | } |
381 | } |
382 | persistApiFilters(); |
383 | return success; |
384 | } |
385 | |
386 | /** |
387 | * Converts the information contained in this filter store to an xml string |
388 | * @return an xml string representation of this filter store |
389 | * @throws CoreException |
390 | */ |
391 | synchronized String getStoreAsXml() throws CoreException { |
392 | if(fFilterMap == null) { |
393 | return null; |
394 | } |
395 | if(fFilterMap.isEmpty()) { |
396 | return null; |
397 | } |
398 | Document document = Util.newDocument(); |
399 | Element root = document.createElement(IApiXmlConstants.ELEMENT_COMPONENT); |
400 | document.appendChild(root); |
401 | root.setAttribute(IApiXmlConstants.ATTR_ID, fProject.getElementName()); |
402 | root.setAttribute(IApiXmlConstants.ATTR_VERSION, IApiXmlConstants.API_FILTER_STORE_CURRENT_VERSION); |
403 | Set allFiltersEntrySet = fFilterMap.entrySet(); |
404 | List allFiltersEntries = new ArrayList(allFiltersEntrySet.size()); |
405 | allFiltersEntries.addAll(allFiltersEntrySet); |
406 | Collections.sort(allFiltersEntries, new Comparator() { |
407 | public int compare(Object o1, Object o2) { |
408 | Map.Entry entry1 = (Map.Entry) o1; |
409 | Map.Entry entry2 = (Map.Entry) o2; |
410 | String path1 = ((IResource) entry1.getKey()).getProjectRelativePath().toOSString(); |
411 | String path2 = ((IResource) entry2.getKey()).getProjectRelativePath().toOSString(); |
412 | return path1.compareTo(path2); |
413 | } |
414 | }); |
415 | for(Iterator iter = allFiltersEntries.iterator(); iter.hasNext();) { |
416 | Map.Entry allFiltersEntry = (Map.Entry) iter.next(); |
417 | IResource resource = (IResource) allFiltersEntry.getKey(); |
418 | Map pTypeNames = (Map) allFiltersEntry.getValue(); |
419 | if(pTypeNames == null) { |
420 | continue; |
421 | } |
422 | Set allTypeNamesEntriesSet = pTypeNames.entrySet(); |
423 | List allTypeNamesEntries = new ArrayList(allTypeNamesEntriesSet.size()); |
424 | allTypeNamesEntries.addAll(allTypeNamesEntriesSet); |
425 | Collections.sort(allTypeNamesEntries, new Comparator() { |
426 | public int compare(Object o1, Object o2) { |
427 | Map.Entry entry1 = (Map.Entry) o1; |
428 | Map.Entry entry2 = (Map.Entry) o2; |
429 | String typeName1 = (String) entry1.getKey(); |
430 | String typeName2 = (String) entry2.getKey(); |
431 | return typeName1.compareTo(typeName2); |
432 | } |
433 | }); |
434 | for (Iterator iterator = allTypeNamesEntries.iterator(); iterator.hasNext(); ) { |
435 | Map.Entry entry = (Map.Entry) iterator.next(); |
436 | String typeName = (String) entry.getKey(); |
437 | Set filters = (Set) entry.getValue(); |
438 | if(filters.isEmpty()) { |
439 | continue; |
440 | } |
441 | Element relement = document.createElement(IApiXmlConstants.ELEMENT_RESOURCE); |
442 | relement.setAttribute(IApiXmlConstants.ATTR_PATH, resource.getProjectRelativePath().toPortableString()); |
443 | boolean typeNameIsInitialized = false; |
444 | if (typeName != GLOBAL) { |
445 | relement.setAttribute(IApiXmlConstants.ATTR_TYPE, typeName); |
446 | typeNameIsInitialized = true; |
447 | } |
448 | root.appendChild(relement); |
449 | typeName = null; |
450 | List filtersList = new ArrayList(filters.size()); |
451 | filtersList.addAll(filters); |
452 | Collections.sort(filtersList, new Comparator(){ |
453 | public int compare(Object o1, Object o2) { |
454 | int problem1Id = ((IApiProblemFilter) o1).getUnderlyingProblem().getId(); |
455 | int problem2Id = ((IApiProblemFilter) o2).getUnderlyingProblem().getId(); |
456 | return problem1Id - problem2Id; |
457 | } |
458 | }); |
459 | for(Iterator iterator2 = filtersList.iterator(); iterator2.hasNext(); ) { |
460 | IApiProblem problem = ((IApiProblemFilter) iterator2.next()).getUnderlyingProblem(); |
461 | typeName = problem.getTypeName(); |
462 | Element filterElement = document.createElement(IApiXmlConstants.ELEMENT_FILTER); |
463 | filterElement.setAttribute(IApiXmlConstants.ATTR_ID, Integer.toString(problem.getId())); |
464 | String[] messageArguments = problem.getMessageArguments(); |
465 | int length = messageArguments.length; |
466 | if(length > 0) { |
467 | Element messageArgumentsElement = document.createElement(IApiXmlConstants.ELEMENT_PROBLEM_MESSAGE_ARGUMENTS); |
468 | for (int j = 0; j < length; j++) { |
469 | Element messageArgumentElement = document.createElement(IApiXmlConstants.ELEMENT_PROBLEM_MESSAGE_ARGUMENT); |
470 | messageArgumentElement.setAttribute(IApiXmlConstants.ATTR_VALUE, String.valueOf(messageArguments[j])); |
471 | messageArgumentsElement.appendChild(messageArgumentElement); |
472 | } |
473 | filterElement.appendChild(messageArgumentsElement); |
474 | } |
475 | relement.appendChild(filterElement); |
476 | } |
477 | if (typeName != null && !typeNameIsInitialized && typeName.length() != 0) { |
478 | relement.setAttribute(IApiXmlConstants.ATTR_TYPE, typeName); |
479 | } |
480 | } |
481 | } |
482 | return Util.serializeDocument(document); |
483 | } |
484 | |
485 | /** |
486 | * Initializes the backing filter map for this store from the .api_filters file. Does nothing if the filter store has already been |
487 | * initialized. |
488 | */ |
489 | private synchronized void initializeApiFilters() { |
490 | if(fFilterMap != null) { |
491 | return; |
492 | } |
493 | if(DEBUG) { |
494 | System.out.println("initializing api filter map for project ["+fProject.getElementName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
495 | } |
496 | fFilterMap = new HashMap(5); |
497 | IPath filepath = getFilterFilePath(true); |
498 | IResource file = ResourcesPlugin.getWorkspace().getRoot().findMember(filepath, true); |
499 | if(file == null) { |
500 | if(DEBUG) { |
501 | System.out.println(".api_filter file not found during initialization for project ["+fProject.getElementName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
502 | } |
503 | return; |
504 | } |
505 | String xml = null; |
506 | InputStream contents = null; |
507 | try { |
508 | IFile filterFile = (IFile)file; |
509 | if (filterFile.exists()) { |
510 | contents = filterFile.getContents(); |
511 | xml = new String(Util.getInputStreamAsCharArray(contents, -1, IApiCoreConstants.UTF_8)); |
512 | } |
513 | } |
514 | catch(CoreException e) { |
515 | ApiPlugin.log(e); |
516 | } |
517 | catch(IOException e) { |
518 | ApiPlugin.log(e); |
519 | } |
520 | finally { |
521 | if (contents != null) { |
522 | try { |
523 | contents.close(); |
524 | } catch(IOException e) { |
525 | // ignore |
526 | } |
527 | } |
528 | } |
529 | if(xml == null) { |
530 | return; |
531 | } |
532 | Element root = null; |
533 | try { |
534 | root = Util.parseDocument(xml); |
535 | } |
536 | catch(CoreException ce) { |
537 | ApiPlugin.log(ce); |
538 | } |
539 | if (root == null) { |
540 | return; |
541 | } |
542 | if (!root.getNodeName().equals(IApiXmlConstants.ELEMENT_COMPONENT)) { |
543 | return; |
544 | } |
545 | String component = root.getAttribute(IApiXmlConstants.ATTR_ID); |
546 | if(component.length() == 0) { |
547 | return; |
548 | } |
549 | String versionValue = root.getAttribute(IApiXmlConstants.ATTR_VERSION); |
550 | int currentVersion = Integer.parseInt(IApiXmlConstants.API_FILTER_STORE_CURRENT_VERSION); |
551 | int version = 0; |
552 | if(versionValue.length() != 0) { |
553 | try { |
554 | version = Integer.parseInt(versionValue); |
555 | } catch (NumberFormatException e) { |
556 | // ignore |
557 | } |
558 | } |
559 | if (version != currentVersion) { |
560 | // we discard all filters since there is no way to retrieve the type name |
561 | fNeedsSaving = true; |
562 | persistApiFilters(); |
563 | return; |
564 | } |
565 | NodeList resources = root.getElementsByTagName(IApiXmlConstants.ELEMENT_RESOURCE); |
566 | ArrayList newfilters = new ArrayList(); |
567 | for(int i = 0; i < resources.getLength(); i++) { |
568 | Element element = (Element) resources.item(i); |
569 | String path = element.getAttribute(IApiXmlConstants.ATTR_PATH); |
570 | if(path.length() == 0) { |
571 | continue; |
572 | } |
573 | String typeName = element.getAttribute(IApiXmlConstants.ATTR_TYPE); |
574 | if (typeName.length() == 0) { |
575 | typeName = null; |
576 | } |
577 | IProject project = (IProject) ResourcesPlugin.getWorkspace().getRoot().findMember(component); |
578 | if(project == null) { |
579 | continue; |
580 | } |
581 | IResource resource = project.findMember(new Path(path)); |
582 | if(resource == null) { |
583 | continue; |
584 | } |
585 | NodeList filters = element.getElementsByTagName(IApiXmlConstants.ELEMENT_FILTER); |
586 | for(int j = 0; j < filters.getLength(); j++) { |
587 | element = (Element) filters.item(j); |
588 | int id = loadIntegerAttribute(element, IApiXmlConstants.ATTR_ID); |
589 | if(id <= 0) { |
590 | continue; |
591 | } |
592 | String[] messageargs = null; |
593 | NodeList elements = element.getElementsByTagName(IApiXmlConstants.ELEMENT_PROBLEM_MESSAGE_ARGUMENTS); |
594 | if (elements.getLength() != 1) continue; |
595 | Element messageArguments = (Element) elements.item(0); |
596 | NodeList arguments = messageArguments.getElementsByTagName(IApiXmlConstants.ELEMENT_PROBLEM_MESSAGE_ARGUMENT); |
597 | int length = arguments.getLength(); |
598 | messageargs = new String[length]; |
599 | for (int k = 0; k < length; k++) { |
600 | Element messageArgument = (Element) arguments.item(k); |
601 | messageargs[k] = messageArgument.getAttribute(IApiXmlConstants.ATTR_VALUE); |
602 | } |
603 | newfilters.add(ApiProblemFactory.newApiProblem(resource.getProjectRelativePath().toPortableString(), |
604 | typeName, |
605 | messageargs, null, null, -1, -1, -1, id)); |
606 | } |
607 | } |
608 | internalAddFilters((IApiProblem[]) newfilters.toArray(new IApiProblem[newfilters.size()]), false); |
609 | newfilters.clear(); |
610 | } |
611 | |
612 | /** |
613 | * Internal use method that allows auto-persisting of the filter file to be turned on or off |
614 | * @param problems the problems to add the the store |
615 | * @param persist if the filters should be auto-persisted after they are added |
616 | */ |
617 | private synchronized void internalAddFilters(IApiProblem[] problems, boolean persist) { |
618 | Set filters = null; |
619 | for(int i = 0; i < problems.length; i++) { |
620 | IApiProblem problem = problems[i]; |
621 | IApiProblemFilter filter = new ApiProblemFilter(fProject.getElementName(), problem); |
622 | String resourcePath = problem.getResourcePath(); |
623 | if (resourcePath == null) { |
624 | continue; |
625 | } |
626 | IResource resource = fProject.getProject().findMember(new Path(resourcePath)); |
627 | if(resource == null) { |
628 | continue; |
629 | } |
630 | Map pTypeNames = (Map) fFilterMap.get(resource); |
631 | String typeName = problem.getTypeName(); |
632 | if (typeName == null) { |
633 | typeName = GLOBAL; |
634 | } |
635 | if(pTypeNames == null) { |
636 | filters = new HashSet(); |
637 | pTypeNames = new HashMap(); |
638 | pTypeNames.put(typeName, filters); |
639 | fFilterMap.put(resource, pTypeNames); |
640 | } else { |
641 | filters = (Set) pTypeNames.get(typeName); |
642 | if (filters == null) { |
643 | filters = new HashSet(); |
644 | pTypeNames.put(typeName, filters); |
645 | } |
646 | } |
647 | fNeedsSaving |= filters.add(filter); |
648 | } |
649 | if(persist) { |
650 | persistApiFilters(); |
651 | } |
652 | } |
653 | /** |
654 | * Loads the specified integer attribute from the given xml element |
655 | * @param element |
656 | * @param name |
657 | * @return |
658 | */ |
659 | private static int loadIntegerAttribute(Element element, String name) { |
660 | String value = element.getAttribute(name); |
661 | if(value.length() == 0) { |
662 | return -1; |
663 | } |
664 | try { |
665 | int number = Integer.parseInt(value); |
666 | return number; |
667 | } |
668 | catch(NumberFormatException nfe) {} |
669 | return -1; |
670 | } |
671 | /** |
672 | * @return the {@link IPath} to the filters file |
673 | */ |
674 | IPath getFilterFilePath(boolean includeproject) { |
675 | if(includeproject) { |
676 | IPath path = fProject.getPath(); |
677 | return path.append(SETTINGS_FOLDER).append(IApiCoreConstants.API_FILTERS_XML_NAME); |
678 | } |
679 | return new Path(SETTINGS_FOLDER).append(IApiCoreConstants.API_FILTERS_XML_NAME); |
680 | } |
681 | /** |
682 | * Start recording filter usage for this store. |
683 | */ |
684 | public synchronized void recordFilterUsage() { |
685 | initializeApiFilters(); |
686 | fUnusedFilters = new HashMap(); |
687 | IResource resource = null; |
688 | Map types = null; |
689 | Set values = null; |
690 | for(Iterator iter = fFilterMap.keySet().iterator(); iter.hasNext();) { |
691 | resource = (IResource) iter.next(); |
692 | types = (Map) fFilterMap.get(resource); |
693 | values = new HashSet(); |
694 | fUnusedFilters.put(resource, values); |
695 | for(Iterator iter2 = types.keySet().iterator(); iter2.hasNext();) { |
696 | values.addAll((Set) types.get(iter2.next())); |
697 | } |
698 | } |
699 | } |
700 | |
701 | /** |
702 | * records that the following filter has been used |
703 | * @param resource |
704 | * @param filter |
705 | */ |
706 | private void recordFilterUsed(IResource resource, IApiProblemFilter filter) { |
707 | if(fUnusedFilters != null) { |
708 | Set unused = (Set) fUnusedFilters.get(resource); |
709 | if(unused != null) { |
710 | unused.remove(filter); |
711 | if(unused.isEmpty()) { |
712 | fUnusedFilters.remove(resource); |
713 | } |
714 | } |
715 | } |
716 | } |
717 | |
718 | /** |
719 | * Returns all of the unused filters for this store at the moment in time this |
720 | * method is called. |
721 | * @return the listing of currently unused filters or an empty list, never <code>null</code> |
722 | */ |
723 | public IApiProblemFilter[] getUnusedFilters(IResource resource, String typeName) { |
724 | if(fUnusedFilters != null) { |
725 | Set unused = new HashSet(); |
726 | Set set = null; |
727 | if(resource != null) { |
728 | // add any unused filters for the resource |
729 | set = (Set) fUnusedFilters.get(resource); |
730 | if (set != null) { |
731 | collectFilterFor(set, typeName, unused); |
732 | } |
733 | if(Util.isManifest(resource.getProjectRelativePath())) { |
734 | //we need to add any filters that are cached for resources |
735 | //that no longer exist - deleted types |
736 | //deleted types are only ever passed in with the manifest associated with them |
737 | IResource res = null; |
738 | for (Iterator iter = fUnusedFilters.keySet().iterator(); iter.hasNext();) { |
739 | res = (IResource) iter.next(); |
740 | if(res.exists()) { |
741 | continue; |
742 | } |
743 | if(!res.getProject().equals(resource.getProject())) { |
744 | continue; |
745 | } |
746 | set = (Set)fUnusedFilters.get(res); |
747 | collectFilterFor(set, typeName, unused); |
748 | } |
749 | } |
750 | } |
751 | else { |
752 | for(Iterator iter = fUnusedFilters.keySet().iterator(); iter.hasNext();) { |
753 | set = (Set) fUnusedFilters.get(iter.next()); |
754 | if(set != null) { |
755 | unused.addAll(set); |
756 | } |
757 | } |
758 | } |
759 | int size = unused.size(); |
760 | if (size == 0) { |
761 | return NO_FILTERS; |
762 | } |
763 | return (IApiProblemFilter[]) unused.toArray(new IApiProblemFilter[size]); |
764 | } |
765 | return NO_FILTERS; |
766 | } |
767 | |
768 | private void collectFilterFor(Set filters, String typename, Set collector) { |
769 | for (Iterator iter = filters.iterator(); iter.hasNext();) { |
770 | ApiProblemFilter filter = (ApiProblemFilter) iter.next(); |
771 | IApiProblem underlyingProblem = filter.getUnderlyingProblem(); |
772 | if (underlyingProblem != null) { |
773 | String underlyingTypeName = underlyingProblem.getTypeName(); |
774 | if (underlyingTypeName != null && (typename == null || underlyingTypeName.equals(typename))) { |
775 | collector.add(filter); |
776 | } |
777 | } |
778 | } |
779 | } |
780 | |
781 | /* (non-Javadoc) |
782 | * @see java.lang.Object#toString() |
783 | */ |
784 | public String toString() { |
785 | return "Api filter store for component: "+fProject.getElementName(); //$NON-NLS-1$ |
786 | } |
787 | |
788 | /* (non-Javadoc) |
789 | * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) |
790 | */ |
791 | public void resourceChanged(IResourceChangeEvent event) { |
792 | if(fTriggeredChange) { |
793 | //eat the event if the deletion / addition / change occurred because we persisted the file |
794 | //via the persistApiFilters(..) method |
795 | //see https://bugs.eclipse.org/bugs/show_bug.cgi?id=222442 |
796 | fTriggeredChange = false; |
797 | if(DEBUG) { |
798 | System.out.println("ignoring trigered change"); //$NON-NLS-1$ |
799 | } |
800 | return; |
801 | } |
802 | if(event.getType() == IResourceChangeEvent.POST_CHANGE) { |
803 | IPath path = getFilterFilePath(true); |
804 | IResourceDelta leafdelta = event.getDelta().findMember(path); |
805 | if(leafdelta == null) { |
806 | return; |
807 | } |
808 | boolean needsbuild = false; |
809 | if(leafdelta.getKind() == IResourceDelta.REMOVED) { |
810 | if(DEBUG) { |
811 | System.out.println("processed REMOVED delta"); //$NON-NLS-1$ |
812 | } |
813 | if(fFilterMap != null) { |
814 | fFilterMap.clear(); |
815 | needsbuild = true; |
816 | } |
817 | } |
818 | else if(leafdelta.getKind() == IResourceDelta.ADDED || |
819 | (leafdelta.getFlags() & IResourceDelta.CONTENT) != 0 || |
820 | (leafdelta.getFlags() & IResourceDelta.REPLACED) != 0) { |
821 | if(DEBUG) { |
822 | System.out.println("processing ADDED or CONTENT or REPLACED"); //$NON-NLS-1$ |
823 | } |
824 | IResource resource = leafdelta.getResource(); |
825 | if(resource != null && resource.getType() == IResource.FILE) { |
826 | if(DEBUG) { |
827 | System.out.println("processed FILE delta for ["+resource.getName()+"]"); //$NON-NLS-1$ //$NON-NLS-2$ |
828 | } |
829 | IFile file = (IFile) resource; |
830 | if(file.isAccessible()) { |
831 | try { |
832 | clearFilters(); |
833 | initializeApiFilters(); |
834 | } |
835 | finally { |
836 | needsbuild = true; |
837 | } |
838 | } |
839 | } |
840 | } |
841 | if(needsbuild && ResourcesPlugin.getWorkspace().isAutoBuilding()) { |
842 | Util.getBuildJob(new IProject[] {fProject.getProject()}, IncrementalProjectBuilder.INCREMENTAL_BUILD).schedule(); |
843 | } |
844 | } |
845 | } |
846 | |
847 | /** |
848 | * Clears out the filter map |
849 | */ |
850 | private synchronized void clearFilters() { |
851 | if(fFilterMap != null) { |
852 | fFilterMap.clear(); |
853 | fFilterMap = null; |
854 | } |
855 | } |
856 | } |