# HG changeset patch # Parent 0606c1414754a89f700a41dc53859d2bd520236b Bug #183794 comment #16: hints to convert layer entries to annotations. diff --git a/apisupport.project/src/org/netbeans/modules/apisupport/project/layers/LayerNode.java b/apisupport.project/src/org/netbeans/modules/apisupport/project/layers/LayerNode.java --- a/apisupport.project/src/org/netbeans/modules/apisupport/project/layers/LayerNode.java +++ b/apisupport.project/src/org/netbeans/modules/apisupport/project/layers/LayerNode.java @@ -79,6 +79,7 @@ import org.openide.util.RequestProcessor; import org.openide.util.actions.SystemAction; import org.openide.util.lookup.Lookups; +import org.openide.util.lookup.ProxyLookup; import org.openide.xml.XMLUtil; /** @@ -96,7 +97,7 @@ } private LayerNode(Node delegate, LayerHandle handle, boolean specialDisplayName) { - super(delegate, new LayerChildren(handle)); + super(delegate, new LayerChildren(handle), new ProxyLookup(delegate.getLookup(), Lookups.singleton(handle))); this.specialDisplayName = specialDisplayName; } diff --git a/apisupport.refactoring/nbproject/project.xml b/apisupport.refactoring/nbproject/project.xml --- a/apisupport.refactoring/nbproject/project.xml +++ b/apisupport.refactoring/nbproject/project.xml @@ -50,6 +50,15 @@ org.netbeans.modules.apisupport.refactoring + org.netbeans.api.annotations.common + + + + 1 + 1.6 + + + org.netbeans.api.java @@ -85,6 +94,24 @@ + org.netbeans.modules.editor.errorstripe.api + + + + 1 + 2.14 + + + + org.netbeans.modules.editor.mimelookup + + + + 1 + 1.20 + + + org.netbeans.modules.java.project @@ -137,6 +164,15 @@ + org.netbeans.spi.editor.hints + + + + 0 + 1.15 + + + org.openide.filesystems diff --git a/apisupport.refactoring/src/org/netbeans/modules/apisupport/hints/ActionRegistrationHinter.java b/apisupport.refactoring/src/org/netbeans/modules/apisupport/hints/ActionRegistrationHinter.java new file mode 100644 --- /dev/null +++ b/apisupport.refactoring/src/org/netbeans/modules/apisupport/hints/ActionRegistrationHinter.java @@ -0,0 +1,84 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.apisupport.hints; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import org.netbeans.api.java.source.Task; +import org.netbeans.api.java.source.WorkingCopy; +import org.netbeans.spi.editor.hints.ChangeInfo; +import org.netbeans.spi.editor.hints.Fix; +import org.netbeans.spi.editor.hints.Severity; +import org.openide.util.lookup.ServiceProvider; + +/** + * Transformations for #183794: {@link org.openide.awt.ActionRegistration} + */ +@ServiceProvider(service=Hinter.class) +public class ActionRegistrationHinter implements Hinter { + + public @Override void process(final Context ctx) throws Exception { + if ("method:org.openide.awt.Actions.alwaysEnabled".equals(ctx.instanceFile().getAttribute("literal:instanceCreate"))) { + ctx.addHint(Severity.WARNING, /*XXX I18N*/"Use of layer entry where annotation is available", new Fix() { + public @Override String getText() { + return "Convert registration to annotation"; + } + public @Override ChangeInfo implement() throws Exception { + ctx.runModificationTask(new Task() { + public @Override void run(WorkingCopy wc) throws Exception { + Element decl = ctx.findDeclaration(wc, ctx.instanceFile().getAttribute("literal:delegate")); + if (decl == null) { + return; + } + // XXX add @ActionRegistration, @ActionID, @ActionReference + } + }); + return null; + } + }); + } + } + +} diff --git a/apisupport.refactoring/src/org/netbeans/modules/apisupport/hints/ConvertToAnnotationHint.java b/apisupport.refactoring/src/org/netbeans/modules/apisupport/hints/ConvertToAnnotationHint.java new file mode 100644 --- /dev/null +++ b/apisupport.refactoring/src/org/netbeans/modules/apisupport/hints/ConvertToAnnotationHint.java @@ -0,0 +1,210 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.apisupport.hints; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.swing.text.Document; +import javax.xml.parsers.SAXParser; +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.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.NbCollections; +import org.openide.util.RequestProcessor; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.ext.DefaultHandler2; + +@MimeRegistration(mimeType="text/xml", service=UpToDateStatusProviderFactory.class) +public class ConvertToAnnotationHint implements UpToDateStatusProviderFactory { + + private static final RequestProcessor RP = new RequestProcessor(ConvertToAnnotationHint.class); + + 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) { + Exceptions.printStackTrace(x); + return null; + } + } else { + return null; + } + LayerHandle handle = xml.getNodeDelegate().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 LayerHandle handle; + private boolean processed; + private final FileChangeListener listener = new FileChangeAdapter() { + public @Override void fileChanged(FileEvent fe) { + processed = false; + RP.post(Prov.this); // XXX use schedule on a Task + firePropertyChange(PROP_UP_TO_DATE, null, null); + } + }; + + Prov(Document doc, DataObject xml, LayerHandle handle) { + this.doc = doc; + this.handle = handle; + xml.getPrimaryFile().addFileChangeListener(FileUtil.weakFileChangeListener(listener, xml.getPrimaryFile())); + 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; + } + + public @Override void run() { + FileSystem fs = handle.layer(false); + if (fs == null) { + return; + } + Set instances = new LinkedHashSet(); + // Compare AbstractRefactoringPlugin.checkFileObject: + FILE: for (FileObject f : NbCollections.iterable(fs.getRoot().getData(true))) { + if (!f.hasExt("instance")) { + continue; // not supporting *.settings etc. for now + } + instances.add(f); + } + List errors = new ArrayList(); + if (!instances.isEmpty()) { + final Map lines = new HashMap(); + try { // Adapted from OpenLayerFilesAction.openLayerFileAndFind: + InputSource in = new InputSource(handle.getLayerFile().getURL().toExternalForm()); + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser parser = factory.newSAXParser(); + class Handler extends 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); + } + } + DefaultHandler2 handler = new Handler(); + parser.getXMLReader().setProperty("http://xml.org/sax/properties/lexical-handler", handler); // NOI18N + parser.parse(in, handler); + } catch (Exception x) { + Exceptions.printStackTrace(x); + } + for (FileObject instance : instances) { + Integer line = lines.get(instance.getPath()); + if (line == null) { + System.err.println("XXX no line for " + instance.getPath()); + continue; + } + for (Hinter hinter : Lookup.getDefault().lookupAll(Hinter.class)) { + try { + hinter.process(new Hinter.Context(doc, handle, instance, line, errors)); + } catch (Exception x) { + Exceptions.printStackTrace(x); + } + } + } + } + HintsController.setErrors(doc, ConvertToAnnotationHint.class.getName(), errors); + processed = true; + firePropertyChange(PROP_UP_TO_DATE, null, null); + } + + } + +} diff --git a/apisupport.refactoring/src/org/netbeans/modules/apisupport/hints/Hinter.java b/apisupport.refactoring/src/org/netbeans/modules/apisupport/hints/Hinter.java new file mode 100644 --- /dev/null +++ b/apisupport.refactoring/src/org/netbeans/modules/apisupport/hints/Hinter.java @@ -0,0 +1,156 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2010 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2010 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.apisupport.hints; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.swing.text.Document; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.api.java.source.ClasspathInfo; +import org.netbeans.api.java.source.JavaSource; +import org.netbeans.api.java.source.Task; +import org.netbeans.api.java.source.WorkingCopy; +import org.netbeans.modules.apisupport.project.api.LayerHandle; +import org.netbeans.spi.editor.hints.ErrorDescription; +import org.netbeans.spi.editor.hints.ErrorDescriptionFactory; +import org.netbeans.spi.editor.hints.Fix; +import org.netbeans.spi.editor.hints.Severity; +import org.openide.filesystems.FileObject; + +/** + * One category of hint. + * Register implementation into global lookup. + */ +public interface Hinter { + + /** + * Check for hints. + * @param ctx context of a single layer entry + * @throws Exception in case of problem + */ + void process(Context ctx) throws Exception; + + /** + * Context supplied to a {@link Hinter}. + */ + class Context { + + private final Document doc; + private final LayerHandle layer; + private final FileObject instanceFile; + private final int line; + private final List errors; + + Context(Document doc, LayerHandle layer, FileObject instanceFile, int line, List errors) { + this.doc = doc; + this.layer = layer; + this.instanceFile = instanceFile; + this.line = line; + this.errors = errors; + } + + /** + * Gets the layer entry you may offer hints for. + * File attribute names like {@code literal:instanceCreate} may return values like {@code new:pkg.Clazz} or {@code method:pkg.Clazz.factory}. + * @return a {@code *.instance} file in the project's layer + */ + public FileObject instanceFile() { + return instanceFile; + } + + /** + * Add a hint. + * @param severity whether to treat as a warning, etc. + * @param description description of hint + * @param fixes any fixes to offer + */ + public void addHint(Severity severity, String description, Fix... fixes) { + errors.add(ErrorDescriptionFactory.createErrorDescription(severity, description, Arrays.asList(fixes), doc, line)); + } + + /** + * Runs (and commits) a Java source modification task in the context of the project with this layer. + * @param task a task to run + * @throws IOException if it could not be run + */ + public void runModificationTask(Task task) throws IOException { + JavaSource.create(ClasspathInfo.create(layer.getLayerFile())).runModificationTask(task).commit(); + } + + /** + * Locate the declaration of an object declared as a newvalue or methodvalue attribute. + * @param wc as per {@link #runModificationTask} + * @param instanceAttribute the result of {@link FileObject#getAttribute} on a {@code literal:*} key + * @return the corresponding declaration, or null if not found + */ + public @CheckForNull Element findDeclaration(WorkingCopy wc, @NullAllowed Object instanceAttribute) { + if (!(instanceAttribute instanceof String)) { + return null; + } + String attr = (String) instanceAttribute; + if (attr.startsWith("new:")) { + return wc.getElements().getTypeElement(attr.substring(4).replace('$', '.')); + } else if (attr.startsWith("method:")) { + int dot = attr.lastIndexOf('.'); + TypeElement type = wc.getElements().getTypeElement(attr.substring(7, dot).replace('$', '.')); + if (type != null) { + String meth = attr.substring(dot + 1); + for (Element check : type.getEnclosedElements()) { + if (check.getKind() == ElementKind.METHOD && check.getSimpleName().contentEquals(meth) && ((ExecutableElement) check).getParameters().isEmpty()) { + return check; + } + } + } + } + return null; + } + + } + +}