| /* |
| * 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.search; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import org.netbeans.api.actions.Savable; |
| import org.netbeans.api.progress.ProgressHandle; |
| import org.netbeans.api.progress.ProgressHandleFactory; |
| import org.netbeans.api.search.provider.SearchListener; |
| import org.netbeans.modules.search.ui.FileObjectPropertySet; |
| import org.netbeans.modules.search.ui.UiUtils; |
| import org.netbeans.spi.search.SearchFilterDefinition; |
| import org.netbeans.spi.search.provider.SearchComposition; |
| import org.openide.filesystems.FileObject; |
| import org.openide.nodes.AbstractNode; |
| import org.openide.nodes.Children; |
| import org.openide.nodes.Node; |
| import org.openide.util.Cancellable; |
| import org.openide.util.NbBundle; |
| |
| /** |
| * |
| * @author jhavlin |
| */ |
| class GraphicalSearchListener extends SearchListener { |
| |
| private static final int INFO_EVENT_LIMIT = 100; |
| private static final Logger LOG = Logger.getLogger( |
| GraphicalSearchListener.class.getName()); |
| |
| /** |
| * Limit for length of path shown in the progress bar. |
| */ |
| private static final int PATH_LENGTH_LIMIT = 153; |
| /** |
| * Underlying search composition. |
| */ |
| private SearchComposition<?> searchComposition; |
| /** |
| * Progress handle instance. |
| */ |
| private ProgressHandle progressHandle; |
| /** |
| * String in the middle of long text, usually three dots (...). |
| */ |
| private String longTextMiddle = null; |
| |
| private ResultViewPanel resultViewPanel; |
| |
| private RootInfoNode rootInfoNode; |
| private EventChildren eventChildren; |
| |
| public GraphicalSearchListener(SearchComposition<?> searchComposition, |
| ResultViewPanel resultViewPanel) { |
| this.searchComposition = searchComposition; |
| this.resultViewPanel = resultViewPanel; |
| this.rootInfoNode = new RootInfoNode(); |
| } |
| |
| public void searchStarted() { |
| |
| progressHandle = ProgressHandleFactory.createHandle( |
| NbBundle.getMessage(ResultView.class, |
| "TEXT_SEARCHING___"), new Cancellable() { //NOI18N |
| |
| @Override |
| public boolean cancel() { |
| searchComposition.terminate(); |
| return true; |
| } |
| }); |
| progressHandle.start(); |
| resultViewPanel.searchStarted(); |
| searchComposition.getSearchResultsDisplayer().searchStarted(); |
| Collection<? extends Savable> unsaved = |
| Savable.REGISTRY.lookupAll(Savable.class); |
| if (unsaved.size() > 0) { |
| String msg = NbBundle.getMessage(ResultView.class, |
| "TEXT_INFO_WARNING_UNSAVED"); |
| eventChildren.addEvent(new EventNode(EventType.WARNING, msg)); |
| } |
| } |
| |
| public void searchFinished() { |
| if (progressHandle != null) { |
| progressHandle.finish(); |
| progressHandle = null; |
| } |
| resultViewPanel.searchFinished(); |
| searchComposition.getSearchResultsDisplayer().searchFinished(); |
| } |
| |
| @Override |
| public void directoryEntered(String path) { |
| if (progressHandle != null) { |
| progressHandle.progress(shortenPath(path)); |
| } |
| } |
| |
| @Override |
| public void fileContentMatchingStarted(String fileName) { |
| if (progressHandle != null) { |
| progressHandle.progress(shortenPath(fileName)); |
| } |
| } |
| |
| /** |
| * Shorten long part string |
| */ |
| private String shortenPath(String p) { |
| if (p.length() <= PATH_LENGTH_LIMIT) { |
| return p; |
| } else { |
| String mid = getLongTextMiddle(); |
| int halfLength = (PATH_LENGTH_LIMIT - mid.length()) / 2; |
| return p.substring(0, halfLength) + mid |
| + p.substring(p.length() - halfLength); |
| } |
| } |
| |
| /** |
| * Get text replacement for middle part of long strings. |
| */ |
| private String getLongTextMiddle() { |
| if (longTextMiddle == null) { |
| longTextMiddle = NbBundle.getMessage(SearchTask.class, |
| "TEXT_SEARCH_LONG_STRING_MIDDLE"); //NOI18N |
| } |
| return longTextMiddle; |
| } |
| |
| @Override |
| public void generalError(Throwable t) { |
| String msg = NbBundle.getMessage(ResultView.class, |
| "TEXT_INFO_ERROR", t.getMessage()); //NOI18N |
| eventChildren.addEvent(new EventNode(EventType.ERROR, msg)); |
| LOG.log(Level.INFO, t.getMessage(), t); |
| } |
| |
| @Override |
| public void fileContentMatchingError(String path, Throwable t) { |
| String msg = NbBundle.getMessage(ResultView.class, |
| "TEXT_INFO_ERROR_MATCHING", fileName(path), //NOI18N |
| t.getMessage()); |
| String tooltip = NbBundle.getMessage(ResultView.class, |
| "TEXT_INFO_ERROR_MATCHING", path, //NOI18N |
| t.getMessage()); |
| eventChildren.addEvent(new PathEventNode(EventType.ERROR, msg, path, |
| tooltip)); |
| String logMsg = path + ": " + t.getMessage(); //NOI18N |
| if (LOG.isLoggable(Level.FINE)) { |
| LOG.log(Level.FINE, logMsg, t); |
| } else { |
| LOG.log(Level.INFO, logMsg); |
| } |
| } |
| |
| /** |
| * Extract file name from file path. |
| */ |
| private String fileName(String filePath) { |
| Pattern p = Pattern.compile("(/|\\\\)([^/\\\\]+)(/|\\\\)?$"); //NOI18N |
| Matcher m = p.matcher(filePath); |
| if (m.find()) { |
| return m.group(2); |
| } else { |
| return filePath; |
| } |
| } |
| |
| @Override |
| public void fileSkipped(FileObject fileObject, |
| SearchFilterDefinition filter, String message) { |
| fileSkipped(fileObject.toURI(), filter, message); |
| } |
| |
| @Override |
| public void fileSkipped(URI uri, SearchFilterDefinition filter, |
| String message) { |
| LOG.log(Level.FINE, "{0} skipped {1} {2}", new Object[]{ //NOI18N |
| uri.toString(), |
| filter != null ? filter.getClass().getName() : "", //NOI18N |
| message != null ? message : ""}); //NOI18N |
| } |
| |
| public Node getInfoNode() { |
| return rootInfoNode; |
| } |
| |
| private class RootInfoNode extends AbstractNode { |
| |
| public RootInfoNode() { |
| this(new EventChildren()); |
| } |
| |
| private RootInfoNode(EventChildren eventChildren) { |
| |
| super(eventChildren); |
| GraphicalSearchListener.this.eventChildren = eventChildren; |
| setDisplayName(UiUtils.getText("TEXT_INFO_TITLE")); //TODO |
| setIcon(EventType.INFO); |
| } |
| |
| public final void setIcon(EventType mostSeriousEventType) { |
| setIconBaseWithExtension(getIconForEventType(mostSeriousEventType)); |
| } |
| |
| public void fireUpdate() { |
| } |
| } |
| |
| private enum EventType { |
| |
| INFO(1), WARNING(2), ERROR(3); |
| private int badness; |
| |
| private EventType(int badness) { |
| this.badness = badness; |
| } |
| |
| public boolean worseThan(EventType eventType) { |
| return this.badness > eventType.badness; |
| } |
| } |
| |
| private class EventChildren extends Children.Keys<EventNode> { |
| |
| private List<EventNode> events = new ArrayList<EventNode>(); |
| EventType worstType = EventType.INFO; |
| |
| public synchronized void addEvent(EventNode event) { |
| if (events.size() < INFO_EVENT_LIMIT) { |
| this.events.add(event); |
| setKeys(events); |
| if (event.getType().worseThan(worstType)) { |
| worstType = event.getType(); |
| } |
| rootInfoNode.setIconBaseWithExtension( |
| getIconForEventType(worstType)); |
| } else if (events.size() == INFO_EVENT_LIMIT) { |
| this.events.add(new EventNode(EventType.INFO, |
| UiUtils.getText("TEXT_INFO_LIMIT_REACHED"))); //NOI18N |
| setKeys(events); |
| } |
| } |
| |
| @Override |
| protected Node[] createNodes(EventNode key) { |
| return new Node[]{key}; |
| } |
| } |
| |
| private static String getIconForEventType(EventType eventType) { |
| |
| String iconBase = "org/netbeans/modules/search/res/"; //NOI18N |
| String icon; |
| |
| switch (eventType) { |
| case INFO: |
| icon = "info.png"; //NOI18N |
| break; |
| case WARNING: |
| icon = "warning.gif"; //NOI18N |
| break; |
| case ERROR: |
| icon = "error.gif"; //NOI18N |
| break; |
| default: |
| icon = "info.png"; //NOI18N |
| } |
| return iconBase + icon; |
| } |
| |
| private class FileObjectEventNode extends EventNode { |
| |
| private FileObject fileObject; |
| |
| public FileObjectEventNode(EventType type, String message, |
| FileObject fileObject) { |
| super(type, message); |
| this.fileObject = fileObject; |
| } |
| |
| @Override |
| public PropertySet[] createPropertySets() { |
| PropertySet[] propertySets; |
| propertySets = new PropertySet[1]; |
| propertySets[0] = new FileObjectPropertySet(fileObject); |
| return propertySets; |
| } |
| } |
| |
| private class PathEventNode extends EventNode { |
| |
| private String path; |
| private String tooltip; |
| |
| public PathEventNode(EventType type, String message, String path, |
| String tooltip) { |
| super(type, message); |
| this.path = path; |
| this.tooltip = tooltip; |
| } |
| |
| @Override |
| public String getShortDescription() { |
| return tooltip; |
| } |
| |
| @Override |
| public PropertySet[] createPropertySets() { |
| final Property<String> pathProperty = new Property<String>( |
| String.class) { |
| @Override |
| public boolean canRead() { |
| return true; |
| } |
| |
| @Override |
| public String getValue() throws IllegalAccessException, |
| InvocationTargetException { |
| return path; |
| } |
| |
| @Override |
| public boolean canWrite() { |
| return false; |
| } |
| |
| @Override |
| public void setValue(String val) throws IllegalAccessException, |
| IllegalArgumentException, InvocationTargetException { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public String getName() { |
| return "path"; //NOI18N |
| } |
| }; |
| final Property<?>[] properties = new Property<?>[]{pathProperty}; |
| PropertySet[] sets = new PropertySet[1]; |
| sets[0] = new PropertySet() { |
| @Override |
| public Property<?>[] getProperties() { |
| return properties; |
| } |
| }; |
| return sets; |
| } |
| } |
| |
| private class EventNode extends AbstractNode { |
| |
| private PropertySet[] propertySets; |
| private EventType type; |
| |
| public EventNode(EventType type, String message) { |
| super(Children.LEAF); |
| this.type = type; |
| this.setDisplayName(message); |
| this.setIconBaseWithExtension(getIconForEventType(type)); |
| } |
| |
| @Override |
| public PropertySet[] getPropertySets() { |
| if (propertySets == null) { |
| propertySets = createPropertySets(); |
| } |
| return propertySets; |
| } |
| |
| protected PropertySet[] createPropertySets() { |
| return new PropertySet[0]; |
| } |
| |
| public EventType getType() { |
| return type; |
| } |
| } |
| } |