1 /*******************************************************************************
2 * Copyright (c) 2000, 2011 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
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.corext.javadoc;
13 import java.io.ByteArrayInputStream;
15 import java.io.FileReader;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.Reader;
19 import java.io.StringReader;
20 import java.io.UnsupportedEncodingException;
21 import java.lang.reflect.InvocationTargetException;
22 import java.net.MalformedURLException;
24 import java.util.HashMap;
27 import javax.xml.parsers.DocumentBuilder;
28 import javax.xml.parsers.DocumentBuilderFactory;
29 import javax.xml.parsers.ParserConfigurationException;
31 import org.xml.sax.InputSource;
32 import org.xml.sax.SAXException;
33 import org.xml.sax.helpers.DefaultHandler;
35 import org.w3c.dom.Element;
36 import org.w3c.dom.Node;
37 import org.w3c.dom.NodeList;
39 import org.eclipse.core.runtime.CoreException;
40 import org.eclipse.core.runtime.IPath;
41 import org.eclipse.core.runtime.IProgressMonitor;
42 import org.eclipse.core.runtime.IStatus;
43 import org.eclipse.core.runtime.Path;
44 import org.eclipse.core.runtime.QualifiedName;
45 import org.eclipse.core.runtime.Status;
46 import org.eclipse.core.runtime.SubProgressMonitor;
47 import org.eclipse.core.runtime.jobs.Job;
49 import org.eclipse.core.resources.IResource;
50 import org.eclipse.core.resources.IWorkspaceRoot;
51 import org.eclipse.core.resources.IWorkspaceRunnable;
52 import org.eclipse.core.resources.ResourcesPlugin;
54 import org.eclipse.jface.preference.IPreferenceStore;
56 import org.eclipse.ui.PlatformUI;
58 import org.eclipse.jdt.core.Flags;
59 import org.eclipse.jdt.core.IClassFile;
60 import org.eclipse.jdt.core.IClasspathAttribute;
61 import org.eclipse.jdt.core.IClasspathContainer;
62 import org.eclipse.jdt.core.IClasspathEntry;
63 import org.eclipse.jdt.core.ICompilationUnit;
64 import org.eclipse.jdt.core.IField;
65 import org.eclipse.jdt.core.IImportDeclaration;
66 import org.eclipse.jdt.core.IJavaElement;
67 import org.eclipse.jdt.core.IJavaProject;
68 import org.eclipse.jdt.core.IMember;
69 import org.eclipse.jdt.core.IMethod;
70 import org.eclipse.jdt.core.IPackageFragment;
71 import org.eclipse.jdt.core.IPackageFragmentRoot;
72 import org.eclipse.jdt.core.IType;
73 import org.eclipse.jdt.core.JavaCore;
74 import org.eclipse.jdt.core.JavaModelException;
75 import org.eclipse.jdt.core.Signature;
77 import org.eclipse.jdt.internal.corext.CorextMessages;
78 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
80 import org.eclipse.jdt.ui.JavaUI;
81 import org.eclipse.jdt.ui.PreferenceConstants;
83 import org.eclipse.jdt.internal.ui.JavaPlugin;
84 import org.eclipse.jdt.internal.ui.JavaUIException;
85 import org.eclipse.jdt.internal.ui.JavaUIStatus;
86 import org.eclipse.jdt.internal.ui.actions.WorkbenchRunnableAdapter;
87 import org.eclipse.jdt.internal.ui.wizards.buildpaths.BuildPathSupport;
88 import org.eclipse.jdt.internal.ui.wizards.buildpaths.CPListElement;
91 public class JavaDocLocations {
93 private static final String JAR_PROTOCOL= "jar"; //$NON-NLS-1$
94 public static final String ARCHIVE_PREFIX= "jar:"; //$NON-NLS-1$
95 private static final String PREF_JAVADOCLOCATIONS= "org.eclipse.jdt.ui.javadoclocations"; //$NON-NLS-1$
96 public static final String PREF_JAVADOCLOCATIONS_MIGRATED= "org.eclipse.jdt.ui.javadoclocations.migrated"; //$NON-NLS-1$
99 private static final String NODE_ROOT= "javadoclocation"; //$NON-NLS-1$
100 private static final String NODE_ENTRY= "location_01"; //$NON-NLS-1$
101 private static final String NODE_PATH= "path"; //$NON-NLS-1$
102 private static final String NODE_URL= "url"; //$NON-NLS-1$
104 private static final QualifiedName PROJECT_JAVADOC= new QualifiedName(JavaUI.ID_PLUGIN, "project_javadoc_location"); //$NON-NLS-1$
106 public static void migrateToClasspathAttributes() {
107 final Map<IPath, String> oldLocations= loadOldForCompatibility();
108 if (oldLocations.isEmpty()) {
109 IPreferenceStore preferenceStore= PreferenceConstants.getPreferenceStore();
110 preferenceStore.setValue(PREF_JAVADOCLOCATIONS, ""); //$NON-NLS-1$
111 preferenceStore.setValue(PREF_JAVADOCLOCATIONS_MIGRATED, true);
115 Job job= new Job(CorextMessages.JavaDocLocations_migratejob_name) {
117 protected IStatus run(IProgressMonitor monitor) {
119 IWorkspaceRunnable runnable= new IWorkspaceRunnable() {
120 public void run(IProgressMonitor pm) throws CoreException {
121 updateClasspathEntries(oldLocations, pm);
122 IPreferenceStore preferenceStore= PreferenceConstants.getPreferenceStore();
123 preferenceStore.setValue(PREF_JAVADOCLOCATIONS, ""); //$NON-NLS-1$
124 preferenceStore.setValue(PREF_JAVADOCLOCATIONS_MIGRATED, true);
127 new WorkbenchRunnableAdapter(runnable).run(monitor);
128 } catch (InvocationTargetException e) {
130 } catch (InterruptedException e) {
131 // should not happen, cannot cancel
133 return Status.OK_STATUS;
139 final static void updateClasspathEntries(Map<IPath, String> oldLocationMap, IProgressMonitor monitor) throws JavaModelException {
140 IWorkspaceRoot root= ResourcesPlugin.getWorkspace().getRoot();
141 IJavaProject[] javaProjects= JavaCore.create(root).getJavaProjects();
143 monitor.beginTask(CorextMessages.JavaDocLocations_migrate_operation, javaProjects.length);
144 for (int i= 0; i < javaProjects.length; i++) {
145 IJavaProject project= javaProjects[i];
146 String projectJavadoc= oldLocationMap.get(project.getPath());
147 if (projectJavadoc != null) {
149 setProjectJavadocLocation(project, projectJavadoc);
150 } catch (CoreException e) {
155 IClasspathEntry[] rawClasspath= project.getRawClasspath();
156 boolean hasChange= false;
157 for (int k= 0; k < rawClasspath.length; k++) {
158 IClasspathEntry updated= getConvertedEntry(rawClasspath[k], project, oldLocationMap);
159 if (updated != null) {
160 rawClasspath[k]= updated;
165 project.setRawClasspath(rawClasspath, new SubProgressMonitor(monitor, 1));
175 private static IClasspathEntry getConvertedEntry(IClasspathEntry entry, IJavaProject project, Map<IPath, String> oldLocationMap) {
177 switch (entry.getEntryKind()) {
178 case IClasspathEntry.CPE_SOURCE:
179 case IClasspathEntry.CPE_PROJECT:
181 case IClasspathEntry.CPE_CONTAINER:
182 convertContainer(entry, project, oldLocationMap);
184 case IClasspathEntry.CPE_LIBRARY:
185 path= entry.getPath();
187 case IClasspathEntry.CPE_VARIABLE:
188 path= JavaCore.getResolvedVariablePath(entry.getPath());
196 IClasspathAttribute[] extraAttributes= entry.getExtraAttributes();
197 for (int i= 0; i < extraAttributes.length; i++) {
198 if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME.equals(extraAttributes[i].getName())) {
202 String libraryJavadocLocation= oldLocationMap.get(path);
203 if (libraryJavadocLocation != null) {
204 CPListElement element= CPListElement.createFromExisting(entry, project);
205 element.setAttribute(CPListElement.JAVADOC, libraryJavadocLocation);
206 return element.getClasspathEntry();
211 private static void convertContainer(IClasspathEntry entry, IJavaProject project, Map<IPath, String> oldLocationMap) {
213 IClasspathContainer container= JavaCore.getClasspathContainer(entry.getPath(), project);
214 if (container == null) {
218 IClasspathEntry[] entries= container.getClasspathEntries();
219 boolean hasChange= false;
220 for (int i= 0; i < entries.length; i++) {
221 IClasspathEntry curr= entries[i];
222 IClasspathEntry updatedEntry= getConvertedEntry(curr, project, oldLocationMap);
223 if (updatedEntry != null) {
224 entries[i]= updatedEntry;
229 BuildPathSupport.requestContainerUpdate(project, container, entries);
231 } catch (CoreException e) {
237 * Sets the Javadoc location for an archive with the given path.
238 * @param project the Java project
239 * @param url the Javadoc location
241 public static void setProjectJavadocLocation(IJavaProject project, URL url) {
243 String location= url != null ? url.toExternalForm() : null;
244 setProjectJavadocLocation(project, location);
245 } catch (CoreException e) {
250 private static void setProjectJavadocLocation(IJavaProject project, String url) throws CoreException {
251 project.getProject().setPersistentProperty(PROJECT_JAVADOC, url);
254 public static URL getProjectJavadocLocation(IJavaProject project) {
255 if (!project.getProject().isAccessible()) {
259 String prop= project.getProject().getPersistentProperty(PROJECT_JAVADOC);
263 return new URL(prop);
264 } catch (CoreException e) {
266 } catch (MalformedURLException e) {
273 public static URL getLibraryJavadocLocation(IClasspathEntry entry) {
275 throw new IllegalArgumentException("Entry must not be null"); //$NON-NLS-1$
278 int kind= entry.getEntryKind();
279 if (kind != IClasspathEntry.CPE_LIBRARY && kind != IClasspathEntry.CPE_VARIABLE) {
280 throw new IllegalArgumentException("Entry must be of kind CPE_LIBRARY or CPE_VARIABLE"); //$NON-NLS-1$
283 IClasspathAttribute[] extraAttributes= entry.getExtraAttributes();
284 for (int i= 0; i < extraAttributes.length; i++) {
285 IClasspathAttribute attrib= extraAttributes[i];
286 if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME.equals(attrib.getName())) {
288 return new URL(attrib.getValue());
289 } catch (MalformedURLException e) {
297 public static URL getJavadocBaseLocation(IJavaElement element) throws JavaModelException {
298 if (element.getElementType() == IJavaElement.JAVA_PROJECT) {
299 return getProjectJavadocLocation((IJavaProject) element);
302 IPackageFragmentRoot root= JavaModelUtil.getPackageFragmentRoot(element);
307 if (root.getKind() == IPackageFragmentRoot.K_BINARY) {
308 IClasspathEntry entry= root.getResolvedClasspathEntry();
309 URL javadocLocation= getLibraryJavadocLocation(entry);
310 if (javadocLocation != null) {
311 return getLibraryJavadocLocation(entry);
313 entry= root.getRawClasspathEntry();
314 switch (entry.getEntryKind()) {
315 case IClasspathEntry.CPE_LIBRARY:
316 case IClasspathEntry.CPE_VARIABLE:
317 return getLibraryJavadocLocation(entry);
322 return getProjectJavadocLocation(root.getJavaProject());
326 // loading for compatibility
328 private static JavaUIException createException(Throwable t, String message) {
329 return new JavaUIException(JavaUIStatus.createError(IStatus.ERROR, message, t));
332 private static Map<IPath, String> loadOldForCompatibility() {
333 HashMap<IPath, String> resultingOldLocations= new HashMap<IPath, String>();
335 // in 3.0, the javadoc locations were stored as one big string in the preferences
336 String string= PreferenceConstants.getPreferenceStore().getString(PREF_JAVADOCLOCATIONS);
337 if (string != null && string.length() > 0) {
340 bytes= string.getBytes("UTF-8"); //$NON-NLS-1$
341 } catch (UnsupportedEncodingException e) {
342 bytes= string.getBytes();
344 InputStream is= new ByteArrayInputStream(bytes);
346 loadFromStream(new InputSource(is), resultingOldLocations);
347 PreferenceConstants.getPreferenceStore().setValue(PREF_JAVADOCLOCATIONS, ""); //$NON-NLS-1$
348 return resultingOldLocations;
349 } catch (CoreException e) {
350 JavaPlugin.log(e); // log but ignore
354 } catch (IOException e) {
360 // in 2.1, the Javadoc locations were stored in a file in the meta data
361 // note that it is wrong to use a stream reader with XML declaring to be UTF-8
363 final String STORE_FILE= "javadoclocations.xml"; //$NON-NLS-1$
364 File file= JavaPlugin.getDefault().getStateLocation().append(STORE_FILE).toFile();
368 reader= new FileReader(file);
369 loadFromStream(new InputSource(reader), resultingOldLocations);
370 file.delete(); // remove file after successful store
371 return resultingOldLocations;
372 } catch (IOException e) {
373 JavaPlugin.log(e); // log but ignore
376 if (reader != null) {
379 } catch (IOException e) {}
382 } catch (CoreException e) {
383 JavaPlugin.log(e); // log but ignore
386 // in 2.0, the Javadoc locations were stored as one big string in the persistent properties
387 // note that it is wrong to use a stream reader with XML declaring to be UTF-8
389 final QualifiedName QUALIFIED_NAME= new QualifiedName(JavaUI.ID_PLUGIN, "jdoclocation"); //$NON-NLS-1$
391 IWorkspaceRoot root= ResourcesPlugin.getWorkspace().getRoot();
392 String xmlString= root.getPersistentProperty(QUALIFIED_NAME);
393 if (xmlString != null) { // only set when workspace is old
394 Reader reader= new StringReader(xmlString);
396 loadFromStream(new InputSource(reader), resultingOldLocations);
397 root.setPersistentProperty(QUALIFIED_NAME, null); // clear property
398 return resultingOldLocations;
403 } catch (IOException e) {
404 // error closing reader: ignore
408 } catch (CoreException e) {
409 JavaPlugin.log(e); // log but ignore
411 return resultingOldLocations;
414 private static void loadFromStream(InputSource inputSource, Map<IPath, String> oldLocations) throws CoreException {
417 DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
418 parser.setErrorHandler(new DefaultHandler());
419 cpElement = parser.parse(inputSource).getDocumentElement();
420 } catch (SAXException e) {
421 throw createException(e, CorextMessages.JavaDocLocations_error_readXML);
422 } catch (ParserConfigurationException e) {
423 throw createException(e, CorextMessages.JavaDocLocations_error_readXML);
424 } catch (IOException e) {
425 throw createException(e, CorextMessages.JavaDocLocations_error_readXML);
428 if (cpElement == null) return;
429 if (!cpElement.getNodeName().equalsIgnoreCase(NODE_ROOT)) {
432 NodeList list= cpElement.getChildNodes();
433 int length= list.getLength();
434 for (int i= 0; i < length; ++i) {
435 Node node= list.item(i);
436 short type= node.getNodeType();
437 if (type == Node.ELEMENT_NODE) {
438 Element element= (Element) node;
439 if (element.getNodeName().equalsIgnoreCase(NODE_ENTRY)) {
440 String varPath = element.getAttribute(NODE_PATH);
441 String varURL = element.getAttribute(NODE_URL);
443 oldLocations.put(Path.fromPortableString(varPath), varURL);
449 public static URL getJavadocLocation(IJavaElement element, boolean includeMemberReference) throws JavaModelException {
450 URL baseLocation= getJavadocBaseLocation(element);
451 if (baseLocation == null) {
455 String urlString= baseLocation.toExternalForm();
457 StringBuffer pathBuffer= new StringBuffer(urlString);
458 if (!urlString.endsWith("/")) { //$NON-NLS-1$
459 pathBuffer.append('/');
462 switch (element.getElementType()) {
463 case IJavaElement.PACKAGE_FRAGMENT:
464 appendPackageSummaryPath((IPackageFragment) element, pathBuffer);
466 case IJavaElement.JAVA_PROJECT:
467 case IJavaElement.PACKAGE_FRAGMENT_ROOT :
468 appendIndexPath(pathBuffer);
470 case IJavaElement.IMPORT_CONTAINER :
471 element= element.getParent();
473 case IJavaElement.COMPILATION_UNIT :
474 IType mainType= ((ICompilationUnit) element).findPrimaryType();
475 if (mainType == null) {
478 appendTypePath(mainType, pathBuffer);
480 case IJavaElement.CLASS_FILE :
481 appendTypePath(((IClassFile) element).getType(), pathBuffer);
483 case IJavaElement.TYPE :
484 appendTypePath((IType) element, pathBuffer);
486 case IJavaElement.FIELD :
487 IField field= (IField) element;
488 appendTypePath(field.getDeclaringType(), pathBuffer);
489 if (includeMemberReference) {
490 appendFieldReference(field, pathBuffer);
493 case IJavaElement.METHOD :
494 IMethod method= (IMethod) element;
495 appendTypePath(method.getDeclaringType(), pathBuffer);
496 if (includeMemberReference) {
497 appendMethodReference(method, pathBuffer);
500 case IJavaElement.INITIALIZER :
501 appendTypePath(((IMember) element).getDeclaringType(), pathBuffer);
503 case IJavaElement.IMPORT_DECLARATION :
504 IImportDeclaration decl= (IImportDeclaration) element;
506 if (decl.isOnDemand()) {
507 IJavaElement cont= JavaModelUtil.findTypeContainer(element.getJavaProject(), Signature.getQualifier(decl.getElementName()));
508 if (cont instanceof IType) {
509 appendTypePath((IType) cont, pathBuffer);
510 } else if (cont instanceof IPackageFragment) {
511 appendPackageSummaryPath((IPackageFragment) cont, pathBuffer);
514 IType imp= element.getJavaProject().findType(decl.getElementName());
515 appendTypePath(imp, pathBuffer);
518 case IJavaElement.PACKAGE_DECLARATION :
519 IJavaElement pack= element.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
521 appendPackageSummaryPath((IPackageFragment) pack, pathBuffer);
531 return new URL(pathBuffer.toString());
532 } catch (MalformedURLException e) {
538 private static void appendPackageSummaryPath(IPackageFragment pack, StringBuffer buf) {
539 String packPath= pack.getElementName().replace('.', '/');
540 buf.append(packPath);
541 buf.append("/package-summary.html"); //$NON-NLS-1$
544 private static void appendIndexPath(StringBuffer buf) {
545 buf.append("index.html"); //$NON-NLS-1$
548 private static void appendTypePath(IType type, StringBuffer buf) {
549 IPackageFragment pack= type.getPackageFragment();
550 String packPath= pack.getElementName().replace('.', '/');
551 String typePath= type.getTypeQualifiedName('.');
552 if (packPath.length() > 0) {
553 buf.append(packPath);
556 buf.append(typePath);
557 buf.append(".html"); //$NON-NLS-1$
560 private static void appendFieldReference(IField field, StringBuffer buf) {
562 buf.append(field.getElementName());
565 private static void appendMethodReference(IMethod meth, StringBuffer buf) throws JavaModelException {
567 buf.append(meth.getElementName());
570 String[] params= meth.getParameterTypes();
571 IType declaringType= meth.getDeclaringType();
572 boolean isVararg= Flags.isVarargs(meth.getFlags());
573 int lastParam= params.length - 1;
574 for (int i= 0; i <= lastParam; i++) {
576 buf.append(", "); //$NON-NLS-1$
578 String curr= Signature.getTypeErasure(params[i]);
579 String fullName= JavaModelUtil.getResolvedTypeName(curr, declaringType);
580 if (fullName != null) {
581 buf.append(fullName);
582 int dim= Signature.getArrayCount(curr);
583 if (i == lastParam && isVararg) {
587 buf.append("[]"); //$NON-NLS-1$
590 if (i == lastParam && isVararg) {
591 buf.append("..."); //$NON-NLS-1$
598 public static String getBaseURL(IMember member) throws JavaModelException {
599 if (member.isBinary()) {
600 // Source attachment usually does not include Javadoc resources
601 // => Always use the Javadoc location as base:
602 URL baseURL= JavaUI.getJavadocLocation(member, false);
603 if (baseURL != null) {
604 if (baseURL.getProtocol().equals(JAR_PROTOCOL)) {
605 // It's a JarURLConnection, which is not known to the browser widget.
606 // Let's start the help web server:
607 URL baseURL2= PlatformUI.getWorkbench().getHelpSystem().resolve(baseURL.toExternalForm(), true);
608 if (baseURL2 != null) { // can be null if org.eclipse.help.ui is not available
612 return baseURL.toExternalForm();
615 IResource resource= member.getResource();
616 if (resource != null) {
618 * Too bad: Browser widget knows nothing about EFS and custom URL handlers,
619 * so IResource#getLocationURI() does not work in all cases.
620 * We only support the local file system for now.
621 * A solution could be https://bugs.eclipse.org/bugs/show_bug.cgi?id=149022 .
623 IPath location= resource.getLocation();
624 if (location != null)
625 return location.toFile().toURI().toASCIIString();