blob: 268a0a2c0f143e69bc210a9b9f5317d9cc93ade0 [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.apisupport.hints;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.Document;
import javax.xml.parsers.SAXParserFactory;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.apisupport.project.api.LayerHandle;
import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
import org.netbeans.spi.editor.errorstripe.UpToDateStatus;
import org.netbeans.spi.editor.errorstripe.UpToDateStatusProvider;
import org.netbeans.spi.editor.errorstripe.UpToDateStatusProviderFactory;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.netbeans.spi.editor.hints.HintsController;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.util.Lookup;
import org.openide.util.NbCollections;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.xml.EntityCatalog;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.DefaultHandler2;
@MimeRegistration(mimeType="text/x-netbeans-layer+xml", service=UpToDateStatusProviderFactory.class)
public class LayerHints implements UpToDateStatusProviderFactory {
private static final RequestProcessor RP = new RequestProcessor(LayerHints.class);
private static final Logger LOG = Logger.getLogger(LayerHints.class.getName());
public @Override UpToDateStatusProvider createUpToDateStatusProvider(Document doc) {
Object sdp = doc.getProperty(Document.StreamDescriptionProperty); // avoid dep on NbEditorUtilities.getFileObject if possible
DataObject xml;
if (sdp instanceof DataObject) {
xml = (DataObject) sdp;
} else if (sdp instanceof FileObject) {
try {
xml = DataObject.find((FileObject) sdp);
} catch (DataObjectNotFoundException x) {
LOG.log(Level.INFO, null, x);
return null;
}
} else {
return null;
}
if (xml.getPrimaryFile().getNameExt().equals("generated-layer.xml")) { // NOI18N
return null;
}
LayerHandle handle = xml.getLookup().lookup(LayerHandle.class);
if (handle == null) {
return null;
}
Project project = FileOwnerQuery.getOwner(xml.getPrimaryFile());
if (project == null || project.getLookup().lookup(NbModuleProvider.class) == null) {
return null;
}
return new Prov(doc, xml, handle);
}
private static class Prov extends UpToDateStatusProvider implements Runnable {
private final Document doc;
private final DataObject xml;
private final LayerHandle handle;
private boolean processed;
private final RequestProcessor.Task task;
private final FileChangeListener listener = new FileChangeAdapter() {
public @Override void fileChanged(FileEvent fe) {
change();
}
};
private final PropertyChangeListener pcl = new PropertyChangeListener() {
public @Override void propertyChange(PropertyChangeEvent evt) {
if (DataObject.PROP_MODIFIED.equals(evt.getPropertyName())) {
change();
}
}
};
private void change() {
processed = false;
task.schedule(0);
firePropertyChange(PROP_UP_TO_DATE, null, null);
}
Prov(Document doc, DataObject xml, LayerHandle handle) {
this.doc = doc;
this.xml = xml;
this.handle = handle;
xml.getPrimaryFile().addFileChangeListener(FileUtil.weakFileChangeListener(listener, xml.getPrimaryFile()));
xml.addPropertyChangeListener(WeakListeners.propertyChange(pcl, xml));
task = RP.post(this);
}
public @Override UpToDateStatus getUpToDate() {
if (processed) {
return UpToDateStatus.UP_TO_DATE_OK;
}
return processed ? UpToDateStatus.UP_TO_DATE_OK : UpToDateStatus.UP_TO_DATE_PROCESSING;
}
private void cancelAll() {
HintsController.setErrors(doc, LayerHints.class.getName(), Collections.<ErrorDescription>emptyList());
processed = true;
firePropertyChange(PROP_UP_TO_DATE, null, null);
}
public @Override void run() {
if (xml.isModified()) {
cancelAll();
return;
}
FileSystem fs = handle.layer(false);
if (fs == null) {
cancelAll();
return;
}
final URL layerURL = handle.getLayerFile().toURL();
String expectedLayers = "[" + layerURL + "]";
List<ErrorDescription> errors = new ArrayList<ErrorDescription>();
RunnableFuture<Map<String,Integer>> linesFuture = new FutureTask<Map<String,Integer>>(new Callable<Map<String,Integer>>() {
public @Override Map<String,Integer> call() throws Exception {
// Adapted from OpenLayerFilesAction.openLayerFileAndFind:
final Map<String,Integer> lines = new HashMap<String,Integer>();
LOG.log(Level.FINE, "parsing {0}", layerURL);
XMLReader reader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
reader.setContentHandler(new DefaultHandler2() {
private Locator locator;
private String path;
public @Override void setDocumentLocator(Locator l) {
locator = l;
}
public @Override void startElement(String uri, String localname, String qname, Attributes attr) throws SAXException {
if (!qname.matches("file|folder")) { // NOI18N
return;
}
String n = attr.getValue("name"); // NOI18N
path = path == null ? n : path + '/' + n;
lines.put(path, locator.getLineNumber());
}
public @Override void endElement(String uri, String localname, String qname) throws SAXException {
if (!qname.matches("file|folder")) { // NOI18N
return;
}
int slash = path.lastIndexOf('/');
path = slash == -1 ? null : path.substring(0, slash);
}
});
reader.setEntityResolver(EntityCatalog.getDefault());
reader.parse(layerURL.toString());
return lines;
}
});
// Compare AbstractRefactoringPlugin.checkFileObject:
for (FileObject file : NbCollections.iterable(fs.getRoot().getChildren(true))) {
if (!expectedLayers.equals(Arrays.toString((URL[]) file.getAttribute("layers")))) {
LOG.log(Level.FINE, "skipping {0}", file);
continue; // part of generated-layer.xml
}
for (Hinter hinter : Lookup.getDefault().lookupAll(Hinter.class)) {
try {
hinter.process(new Hinter.Context(doc, handle, file, linesFuture, errors));
} catch (Exception x) {
LOG.log(Level.WARNING, null, x);
}
}
}
HintsController.setErrors(doc, LayerHints.class.getName(), errors);
processed = true;
firePropertyChange(PROP_UP_TO_DATE, null, null);
}
}
}