blob: ce3dd96158ab6c2f09d733d41e7a5e5c30d8d40d [file] [log] [blame]
/*
* 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;
}
}
}