| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.web.el.navigation; |
| |
| import com.sun.el.parser.AstIdentifier; |
| import com.sun.el.parser.Node; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.util.SourcePositions; |
| import com.sun.source.util.Trees; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| import javax.lang.model.element.Element; |
| import javax.swing.text.Document; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.ElementHandle; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.SourceUtils; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.api.project.FileOwnerQuery; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.modules.csl.api.DataLoadersBridge; |
| import org.netbeans.modules.csl.api.DeclarationFinder; |
| import org.netbeans.modules.csl.api.ElementKind; |
| import org.netbeans.modules.csl.api.HtmlFormatter; |
| import org.netbeans.modules.csl.api.Modifier; |
| import org.netbeans.modules.csl.api.OffsetRange; |
| import org.netbeans.modules.csl.spi.ParserResult; |
| import org.netbeans.modules.web.common.spi.ProjectWebRootQuery; |
| import org.netbeans.modules.web.el.AstPath; |
| import org.netbeans.modules.web.el.CompilationContext; |
| import org.netbeans.modules.web.el.ELElement; |
| import org.netbeans.modules.web.el.ELTypeUtilities; |
| import org.netbeans.modules.web.el.ResourceBundles; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Pair; |
| |
| /** |
| * Simple DeclarationFinder based on the ELHyperlinkProvider code. |
| * |
| * @author Martin Fousek <marfous@netbeans.org> |
| */ |
| public class ELDeclarationFinder implements DeclarationFinder { |
| |
| private static final org.netbeans.modules.csl.api.ElementHandle DEFAULT_RESOURCE_BUNDLE_HANDLE = new ResourceBundleElementHandle(); |
| |
| @Override |
| public DeclarationLocation findDeclaration(final ParserResult info, int offset) { |
| final Pair<Node, ELElement> nodeElem = ELHyperlinkProvider.resolveNodeAndElement(info.getSnapshot().getSource(), offset, new AtomicBoolean()); |
| if (nodeElem == null) { |
| return DeclarationLocation.NONE; |
| } |
| final FileObject file = info.getSnapshot().getSource().getFileObject(); |
| final ClasspathInfo cp = ELTypeUtilities.getElimplExtendedCPI(file); |
| final RefsHolder refs = new RefsHolder(); |
| final List<AlternativeLocation> alternatives = new ArrayList<>(); |
| try { |
| JavaSource.create(cp).runUserActionTask(new Task<CompilationController>() { |
| @Override |
| public void run(CompilationController cc) throws Exception { |
| cc.toPhase(JavaSource.Phase.RESOLVED); |
| CompilationContext context = CompilationContext.create(file, cc); |
| |
| // resolve beans |
| Element javaElement = ELTypeUtilities.resolveElement(context, nodeElem.second(), nodeElem.first()); |
| if (javaElement != null) { |
| refs.handle = ElementHandle.<Element>create(javaElement); |
| refs.fo = SourceUtils.getFile(refs.handle, cp); |
| } |
| |
| // resolve resource bundles |
| ResourceBundles resourceBundles = ResourceBundles.get(file); |
| if (resourceBundles.canHaveBundles()) { |
| List<ResourceBundles.Location> bundleLocations = getBundleLocations(resourceBundles, nodeElem); |
| if (!bundleLocations.isEmpty()) { |
| refs.fo = bundleLocations.get(0).getFile(); |
| refs.offset = bundleLocations.get(0).getOffset(); |
| for (ResourceBundles.Location location : bundleLocations) { |
| alternatives.add(new ResourceBundleAlternative(location)); |
| } |
| } |
| } |
| } |
| }, true); |
| if (refs.fo != null) { |
| JavaSource javaSource = JavaSource.forFileObject(refs.fo); |
| if (javaSource != null) { |
| // java bean |
| javaSource.runUserActionTask(new Task<CompilationController>() { |
| @Override |
| public void run(CompilationController controller) throws Exception { |
| controller.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); |
| Element element = refs.handle.resolve(controller); |
| Trees trees = controller.getTrees(); |
| Tree tree = trees.getTree(element); |
| SourcePositions sourcePositions = trees.getSourcePositions(); |
| refs.offset = (int) sourcePositions.getStartPosition(controller.getCompilationUnit(), tree); |
| } |
| }, true); |
| } |
| } |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| if (refs.fo != null && refs.offset != -1) { |
| DeclarationLocation declarationLocation = new DeclarationLocation(refs.fo, refs.offset); |
| for (AlternativeLocation alternativeLocation : alternatives) { |
| declarationLocation.addAlternative(alternativeLocation); |
| } |
| return declarationLocation; |
| } |
| return DeclarationLocation.NONE; |
| } |
| |
| @Override |
| public OffsetRange getReferenceSpan(final Document doc, final int caretOffset) { |
| final AtomicReference<OffsetRange> ret = new AtomicReference<>(OffsetRange.NONE); |
| doc.render(new Runnable() { |
| @Override |
| public void run() { |
| int[] offsets = ELHyperlinkProvider.getELIdentifierSpan(doc, caretOffset); |
| if (offsets != null) { |
| ret.set(new OffsetRange(offsets[0], offsets[1])); |
| } |
| } |
| }); |
| return ret.get(); |
| } |
| |
| private static List<ResourceBundles.Location> getBundleLocations(ResourceBundles resourceBundles, Pair<Node, ELElement> nodeElem) { |
| if (nodeElem.first() instanceof AstIdentifier) { |
| return resourceBundles.getLocationsForBundleIdent(nodeElem.first().getImage()); |
| } else { |
| AstPath astPath = new AstPath(nodeElem.second().getNode()); |
| for (Node node : astPath.rootToLeaf()) { |
| String image = nodeElem.first().getImage(); |
| if (node instanceof AstIdentifier && node.getImage() != null && image != null) { |
| if (image.length() > 2 && (image.startsWith("'") && image.endsWith("'")) //NOI18N |
| || (image.startsWith("\"") && image.endsWith("\""))) { //NOI18N |
| // bundle['key'] |
| image = image.substring(1, image.length() - 1); |
| } |
| return resourceBundles.getLocationsForBundleKey(node.getImage(), image); |
| } |
| } |
| } |
| return Collections.<ResourceBundles.Location>emptyList(); |
| } |
| |
| private static class ResourceBundleAlternative implements AlternativeLocation { |
| |
| private final FileObject file; |
| private final int offset; |
| |
| public ResourceBundleAlternative(ResourceBundles.Location location) { |
| this.offset = location.getOffset(); |
| this.file = location.getFile(); |
| } |
| |
| @Override |
| public org.netbeans.modules.csl.api.ElementHandle getElement() { |
| return DEFAULT_RESOURCE_BUNDLE_HANDLE; |
| } |
| |
| @Override |
| public String getDisplayHtml(HtmlFormatter formatter) { |
| StringBuilder b = new StringBuilder(); |
| |
| b.append("<font color=007c00>");//NOI18N |
| b.append("<b>"); //NOI18N |
| b.append(file.getName()); |
| b.append("</b>"); //NOI18N |
| b.append("</font> in "); //NOI18N |
| |
| //add a link to the file relative to the web root |
| FileObject pathRoot = ProjectWebRootQuery.getWebRoot(file); |
| String path = null; |
| if (pathRoot != null) { |
| path = FileUtil.getRelativePath(pathRoot, file); //this may also return null |
| } |
| if (path == null) { |
| Project project = FileOwnerQuery.getOwner(file); |
| if (project != null) { |
| pathRoot = project.getProjectDirectory(); |
| path = FileUtil.getRelativePath(pathRoot, file); //this may also return null |
| } |
| } |
| if (path == null) { |
| //if everything fails, just use the absolute path |
| path = file.getPath(); |
| } |
| |
| b.append("<i>"); //NOI18N |
| b.append(path); |
| b.append("</i>"); //NOI18N |
| if (offset > 0) { |
| b.append(":"); //NOI18N |
| b.append(offset + 1); //line offsets are counted from zero, but in editor lines starts with one. |
| } |
| return b.toString(); |
| } |
| |
| @Override |
| public DeclarationLocation getLocation() { |
| return new DeclarationLocation(file, offset); |
| } |
| |
| @Override |
| public int compareTo(AlternativeLocation o) { |
| return getComparableString(this).compareTo(getComparableString(o)); |
| } |
| |
| private static String getComparableString(AlternativeLocation loc) { |
| DeclarationLocation location = loc.getLocation(); |
| FileObject fileObject = location.getFileObject(); |
| if (fileObject != null) { |
| return String.valueOf(location.getOffset()) + fileObject.getPath(); |
| } else { |
| return String.valueOf(location.getOffset()); |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 5; |
| hash = 89 * hash + Objects.hashCode(this.file); |
| hash = 89 * hash + this.offset; |
| return hash; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| final ResourceBundleAlternative other = (ResourceBundleAlternative) obj; |
| if (!Objects.equals(this.file, other.file)) { |
| return false; |
| } |
| if (this.offset != other.offset) { |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| private static class RefsHolder { |
| |
| private ElementHandle<Element> handle; |
| private FileObject fo; |
| private int offset = -1; |
| } |
| |
| private static class ResourceBundleElementHandle implements org.netbeans.modules.csl.api.ElementHandle { |
| |
| @Override |
| public FileObject getFileObject() { |
| return null; |
| } |
| |
| @Override |
| public String getMimeType() { |
| return ""; |
| } |
| |
| @Override |
| public String getName() { |
| return ""; |
| } |
| |
| @Override |
| public String getIn() { |
| return null; |
| } |
| |
| @Override |
| public ElementKind getKind() { |
| return ElementKind.FILE; |
| } |
| |
| @Override |
| public Set<Modifier> getModifiers() { |
| return Collections.emptySet(); |
| } |
| |
| @Override |
| public boolean signatureEquals(org.netbeans.modules.csl.api.ElementHandle handle) { |
| return false; |
| } |
| |
| @Override |
| public OffsetRange getOffsetRange(ParserResult result) { |
| return OffsetRange.NONE; |
| } |
| } |
| } |