| /* |
| * 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; |
| |
| import com.sun.el.parser.AstIdentifier; |
| import com.sun.el.parser.Node; |
| import com.sun.el.parser.NodeVisitor; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import javax.el.ELException; |
| import javax.lang.model.element.Element; |
| import org.netbeans.api.java.source.ClasspathInfo; |
| import org.netbeans.api.java.source.CompilationController; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.Task; |
| import org.netbeans.modules.csl.api.ColoringAttributes; |
| import org.netbeans.modules.csl.api.OccurrencesFinder; |
| import org.netbeans.modules.csl.api.OffsetRange; |
| import org.netbeans.modules.parsing.spi.Parser.Result; |
| import org.netbeans.modules.parsing.spi.Scheduler; |
| import org.netbeans.modules.parsing.spi.SchedulerEvent; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Pair; |
| |
| /** |
| * Occurrences finder for Expression Language. |
| * |
| * @author Erno Mononen |
| */ |
| final class ELOccurrencesFinder extends OccurrencesFinder { |
| |
| private int caretPosition; |
| private boolean cancelled; |
| private final Map<OffsetRange, ColoringAttributes> occurrences = new HashMap<>(); |
| |
| public ELOccurrencesFinder() { |
| } |
| |
| @Override |
| public void setCaretPosition(int position) { |
| this.caretPosition = position; |
| } |
| |
| @Override |
| public Map getOccurrences() { |
| return occurrences; |
| } |
| |
| @Override |
| public void run(Result result, SchedulerEvent event) { |
| occurrences.clear(); |
| if (checkAndResetCancel()) { |
| return; |
| } |
| computeOccurrences((ELParserResult) result); |
| } |
| |
| @Override |
| public int getPriority() { |
| return 200; // not sure what to return here, 200 this is just a random number. |
| } |
| |
| @Override |
| public Class<? extends Scheduler> getSchedulerClass() { |
| return Scheduler.CURSOR_SENSITIVE_TASK_SCHEDULER; |
| } |
| |
| @Override |
| public void cancel() { |
| this.cancelled = true; |
| } |
| |
| private void computeOccurrences(final ELParserResult parserResult) { |
| ELElement current = parserResult.getElementAt(caretPosition); |
| if (current == null) { |
| return; |
| } |
| final Node targetNode = current.findNodeAt(caretPosition); |
| if (targetNode == null || targetNode.getImage() == null) { |
| return; |
| } |
| final Pair<ELElement,Node> target = Pair.of(current, targetNode); |
| // find other similar nodes |
| final List<Pair<ELElement, Node>> matching = new ArrayList<>(); |
| for (final ELElement eLElement : parserResult.getElements()) { |
| if (checkAndResetCancel()) { |
| return; |
| } |
| if (!eLElement.isValid()) { |
| continue; |
| } |
| eLElement.getNode().accept(new NodeVisitor() { |
| |
| @Override |
| public void visit(Node node) throws ELException { |
| if (node.getClass().equals(targetNode.getClass()) |
| && targetNode.getImage().equals(node.getImage())) { |
| matching.add(Pair.of(eLElement, node)); |
| } |
| } |
| }); |
| } |
| final FileObject file = parserResult.getFileObject(); |
| JavaSource jsource = JavaSource.create(ELTypeUtilities.getElimplExtendedCPI(file)); |
| try { |
| jsource.runUserActionTask(new Task<CompilationController>() { |
| |
| @Override |
| public void run(CompilationController info) throws Exception { |
| info.toPhase(JavaSource.Phase.RESOLVED); |
| occurrences.putAll(findMatchingTypes(CompilationContext.create(file, info), parserResult, target, matching)); |
| |
| } |
| }, true); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| if (this.occurrences.isEmpty()) { |
| // perhaps the caret is on a resource bundle key node |
| occurrences.putAll(findMatchingResourceBundleKeys(target, parserResult)); |
| } |
| } |
| |
| private Map<OffsetRange, ColoringAttributes> findMatchingResourceBundleKeys(Pair<ELElement, Node> target, ELParserResult parserResult) { |
| ResourceBundles resourceBundles = ResourceBundles.get(parserResult.getFileObject()); |
| if (!resourceBundles.canHaveBundles()) { |
| return Collections.emptyMap(); |
| } |
| List<Pair<AstIdentifier, Node>> keys = new ArrayList<>(); |
| // the logic here is a bit strange, maybe should add new methods to ResourceBundles |
| // for a more straightforward computation. |
| // first, check whether the current EL elements has keys |
| keys.addAll(resourceBundles.collectKeys(target.first().getNode())); |
| if (keys.isEmpty()) { |
| return Collections.emptyMap(); |
| } |
| |
| // second, if yes, check whether it has a key matching to the node under the caret |
| boolean found = false; |
| for (Pair<AstIdentifier, Node> pair : keys) { |
| if (pair.second().equals(target.second())) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| return Collections.emptyMap(); |
| } |
| // third: collect the other matching keys and return them |
| Map<OffsetRange, ColoringAttributes> result = new HashMap<>(); |
| for (ELElement each : parserResult.getElements()) { |
| if (!each.isValid()) { |
| continue; |
| } |
| for (Pair<AstIdentifier, Node> candidate : resourceBundles.collectKeys(each.getNode())) { |
| if (candidate.second().equals(target.second())) { |
| OffsetRange range = each.getOriginalOffset(candidate.second()); |
| result.put(range, ColoringAttributes.MARK_OCCURRENCES); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private Map<OffsetRange, ColoringAttributes> findMatchingTypes(CompilationContext info, ELParserResult parserResult, Pair<ELElement,Node> target, List<Pair<ELElement,Node>> candidates) { |
| Element targetType = ELTypeUtilities.resolveElement(info, target.first(), target.second()); |
| Map<OffsetRange, ColoringAttributes> result = new HashMap<>(); |
| |
| for (Pair<ELElement,Node> candidate : candidates) { |
| if (checkAndResetCancel()) { |
| return result; |
| } |
| Element type = ELTypeUtilities.resolveElement(info, candidate.first(), candidate.second()); |
| if (type != null && type.equals(targetType)) { |
| OffsetRange range = candidate.first().getOriginalOffset(candidate.second()); |
| result.put(range, ColoringAttributes.MARK_OCCURRENCES); |
| } |
| } |
| return result; |
| } |
| |
| private boolean checkAndResetCancel() { |
| if (cancelled) { |
| cancelled = false; |
| return true; |
| } |
| return false; |
| } |
| |
| } |