Introducing meta-annotation to create Registration annotation for mime-lookup registration. diff --git a/editor.mimelookup/apichanges.xml b/editor.mimelookup/apichanges.xml --- a/editor.mimelookup/apichanges.xml +++ b/editor.mimelookup/apichanges.xml @@ -108,6 +108,19 @@ + + MimeLookupRegistrationMetaAnnotation annotation added + + + + + + Added MimeLookupRegistrationMetaAnnotation to simplify creation + of annotations for registration to mime lookup. + + + + MimeLookup.getLookup(String mimePath) method added diff --git a/editor.mimelookup/manifest.mf b/editor.mimelookup/manifest.mf --- a/editor.mimelookup/manifest.mf +++ b/editor.mimelookup/manifest.mf @@ -1,7 +1,7 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.editor.mimelookup/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/mimelookup/Bundle.properties -OpenIDE-Module-Specification-Version: 1.18 +OpenIDE-Module-Specification-Version: 1.19 OpenIDE-Module-Recommends: org.netbeans.spi.editor.mimelookup.MimeDataProvider AutoUpdate-Essential-Module: true diff --git a/editor.mimelookup/nbproject/project.xml b/editor.mimelookup/nbproject/project.xml --- a/editor.mimelookup/nbproject/project.xml +++ b/editor.mimelookup/nbproject/project.xml @@ -101,6 +101,11 @@ + + org.openide.util.lookup + + + diff --git a/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/AnnotationProcessor.template b/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/AnnotationProcessor.template new file mode 100644 --- /dev/null +++ b/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/AnnotationProcessor.template @@ -0,0 +1,65 @@ +package __PACKAGE__; + +import java.util.Map.Entry; +import java.util.Set; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic.Kind; +import org.openide.filesystems.annotations.LayerGeneratingProcessor; +import org.openide.filesystems.annotations.LayerGenerationException; +import org.openide.util.lookup.ServiceProvider; + +@SupportedAnnotationTypes("__ANNOTATION_NAME__") +@SupportedSourceVersion(SourceVersion.RELEASE_6) +@ServiceProvider(service=Processor.class) +public class __CLASS_NAME__ extends LayerGeneratingProcessor { + @Override + protected boolean handleProcess(Set annotations, RoundEnvironment roundEnv) throws LayerGenerationException { + TypeElement te = processingEnv.getElementUtils().getTypeElement("__ANNOTATION_NAME__"); + processingEnv.getMessager().printMessage(Kind.NOTE, roundEnv.getElementsAnnotatedWith(te).toString()); + for (Element el : roundEnv.getElementsAnnotatedWith(te)) { + for (AnnotationMirror am : el.getAnnotationMirrors()) { + if (!te.equals(am.getAnnotationType().asElement())) { + continue; + } + String mimeType = null; + int position = Integer.MAX_VALUE; + String[] supersedes = new String[0]; + for (Entry e : am.getElementValues().entrySet()) { + Name simpleName = e.getKey().getSimpleName(); + if (simpleName.contentEquals("mimeType")) { + mimeType = (String) e.getValue().getValue(); + continue; + } + if (simpleName.contentEquals("position")) { + position = (Integer) e.getValue().getValue(); + continue; + } + if (simpleName.contentEquals("supersedes")) { + position = (Integer) e.getValue().getValue(); + continue; + } + } + + if (mimeType != null) { + if (mimeType.length() != 0) mimeType = "/" + mimeType; + layer(el).instanceFile("Editors" + mimeType + "__LAYER_FOLDER__", null, null).position(position).write(); + + for (String superseded : supersedes) { + layer(el).file("Editors" + mimeType + "__LAYER_FOLDER__/" + superseded + "_hidden").write(); + } + } + } + } + return true; + } +} diff --git a/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/Class2LayerFolder.template b/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/Class2LayerFolder.template new file mode 100644 --- /dev/null +++ b/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/Class2LayerFolder.template @@ -0,0 +1,22 @@ +package __PACKAGE__; + +import org.netbeans.spi.editor.mimelookup.Class2LayerFolder; +import org.netbeans.spi.editor.mimelookup.InstanceProvider; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service=Class2LayerFolder.class) +public class __CLASS_NAME__ implements Class2LayerFolder { + + public Class getClazz() { + return __API_CLASS_FQN__.class; + } + + public String getLayerFolderName() { + return "__LAYER_FOLDER__"; + } + + public InstanceProvider getInstanceProvider() { + return null; + } + +} diff --git a/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/CreateRegistrationProcessor.java b/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/CreateRegistrationProcessor.java new file mode 100644 --- /dev/null +++ b/editor.mimelookup/src/org/netbeans/modules/editor/mimelookup/CreateRegistrationProcessor.java @@ -0,0 +1,226 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-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]" + * + * Contributor(s): + * + * Portions Copyrighted 2008-2010 Sun Microsystems, Inc. + */ + +package org.netbeans.modules.editor.mimelookup; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.tools.Diagnostic.Kind; +import javax.tools.JavaFileObject; +import org.openide.util.MapFormat; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author Jan Lahoda + */ +@SupportedAnnotationTypes("org.netbeans.spi.editor.mimelookup.MimeLookupRegistrationMetaAnnotation") +@SupportedSourceVersion(SourceVersion.RELEASE_6) +@ServiceProvider(service=Processor.class) +public class CreateRegistrationProcessor extends AbstractProcessor { + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + TypeElement te = processingEnv.getElementUtils().getTypeElement("org.netbeans.spi.editor.mimelookup.MimeLookupRegistrationMetaAnnotation"); + + for (Element el : roundEnv.getElementsAnnotatedWith(te)) { + for (AnnotationMirror am : el.getAnnotationMirrors()) { + if (!te.equals(am.getAnnotationType().asElement())) { + continue; + } + + String targetPackage = null; + String folderName = ""; + TypeMirror jlObject = processingEnv.getElementUtils().getTypeElement("java.lang.Object").asType(); + TypeMirror clazz = jlObject; + + for (Entry e : am.getElementValues().entrySet()) { + if (e.getKey().getSimpleName().contentEquals("subfolderName")) { + folderName = (String) e.getValue().getValue(); + continue; + } + if (e.getKey().getSimpleName().contentEquals("targetPackage")) { + targetPackage = (String) e.getValue().getValue(); + continue; + } + if (e.getKey().getSimpleName().contentEquals("clazz")) { + clazz = (TypeMirror) e.getValue().getValue(); + continue; + } + } + + boolean foundMimeType = false; + + for (ExecutableElement attr : ElementFilter.methodsIn(el.getEnclosedElements())) { + String simpleName = attr.getSimpleName().toString(); + String expectedType = SUPPORTED_ATTRIBUTES.get(simpleName); + + if (expectedType == null) { + processingEnv.getMessager().printMessage(Kind.WARNING, "Attribute " + simpleName + " not supported by MimeLookupRegistrationMetaAnnotation.", attr); + } else { + if (!expectedType.equals(attr.getReturnType()./*XXX*/toString())) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Attribute " + simpleName + " does not have correct type. Expected: " + expectedType + ", found: " + attr.getReturnType(), attr); + } + } + + foundMimeType |= "mimeType".equals(simpleName); + } + + if (!foundMimeType) { + processingEnv.getMessager().printMessage(Kind.ERROR, "The annotation must specify mimeType attribute with type java.lang.String", el); + } + + try { + createProcessor(targetPackage, (TypeElement) el, folderName); + + if (folderName != null && !jlObject.equals(clazz)) { + createClass2LayerFolder(targetPackage, (TypeElement) el, (TypeElement) ((DeclaredType) clazz).asElement(), folderName); + } + } catch (IOException ex) { + processingEnv.getMessager().printMessage(Kind.ERROR, ex.getMessage()); + } + } + } + + return true; + } + + private void createProcessor(String targetPackage, TypeElement annotation, String folderName) throws IOException { + String classSimpleName = classNamePrefix(annotation) + "Processor"; + + if (folderName.length() > 0) { + folderName = "/" + folderName; + } + + Map arguments = new HashMap(); + + arguments.put("PACKAGE", targetPackage); + arguments.put("CLASS_NAME", classSimpleName); + arguments.put("ANNOTATION_NAME", annotation.getQualifiedName().toString()); + arguments.put("LAYER_FOLDER", folderName); + + MapFormat mf = new MapFormat(arguments); + + mf.setLeftBrace("__"); + mf.setRightBrace("__"); + processTemplate(mf, targetPackage + "." + classSimpleName, "AnnotationProcessor.template", annotation); + } + + private void createClass2LayerFolder(String targetPackage, TypeElement annotation, TypeElement clazz, String folderName) throws IOException { + String classSimpleName = classNamePrefix(annotation) + "Class2LayerFolder"; + Map arguments = new HashMap(); + + arguments.put("PACKAGE", targetPackage); + arguments.put("CLASS_NAME", classSimpleName); + arguments.put("API_CLASS_FQN", clazz.getQualifiedName().toString()); + arguments.put("LAYER_FOLDER", folderName); + + MapFormat mf = new MapFormat(arguments); + + mf.setLeftBrace("__"); + mf.setRightBrace("__"); + processTemplate(mf, targetPackage + "." + classSimpleName, "Class2LayerFolder.template", annotation); + } + + private void processTemplate(MapFormat mf, String targetFileName, String template, TypeElement annotation) throws IOException { + String templateCode = readURL(CreateRegistrationProcessor.class.getResource(template)); + String formated = mf.format(templateCode); + JavaFileObject file = processingEnv.getFiler().createSourceFile(targetFileName, annotation); + Writer w = file.openWriter(); + + try { + w.write(formated); + } finally { + w.close(); + } + } + + private String classNamePrefix(TypeElement te) { + PackageElement p = processingEnv.getElementUtils().getPackageOf(te); + + return te.getQualifiedName().toString().substring(p.getQualifiedName().length()).replaceAll("\\.", ""); + } + + private String readURL(URL url) throws IOException { + StringWriter out = new StringWriter(); + Reader r = null; + + try { + r = new InputStreamReader(url.openStream(), "UTF-8"); + + int read; + + while ((read = r.read()) != (-1)) { + out.write(read); + } + + return out.toString(); + } finally { + if (r != null) { + r.close(); + } + out.close(); + } + } + + private static final Map SUPPORTED_ATTRIBUTES; + + static { + SUPPORTED_ATTRIBUTES = new HashMap(); + + SUPPORTED_ATTRIBUTES.put("mimeType", "java.lang.String"); + SUPPORTED_ATTRIBUTES.put("position", "int"); + SUPPORTED_ATTRIBUTES.put("supersedes", "java.lang.String[]"); + } +} diff --git a/editor.mimelookup/src/org/netbeans/spi/editor/mimelookup/MimeLookupRegistrationMetaAnnotation.java b/editor.mimelookup/src/org/netbeans/spi/editor/mimelookup/MimeLookupRegistrationMetaAnnotation.java new file mode 100644 --- /dev/null +++ b/editor.mimelookup/src/org/netbeans/spi/editor/mimelookup/MimeLookupRegistrationMetaAnnotation.java @@ -0,0 +1,96 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008-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]" + * + * Contributor(s): + * + * Portions Copyrighted 2008-2010 Sun Microsystems, Inc. + */ + +package org.netbeans.spi.editor.mimelookup; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/**Meta-annotation for registration to mime lookup (via system filesystem). A feature + * that requires registration to the mime lookup is expected to create a Registration + * annotation and annotate it with this annotation. An annotation processor is generated + * automatically for such registration annotation. The supported attributes for the registration + * annotations are: "mimeType" (required, type java.lang.String), + * "position" (optional, recommended, type int, should default to Integer.MAX_VALUE), + * and "supersedes" (optional, type java.lang.String[]). + * + * Typical registration annotation: + *
+ *     /**
+ *      * Registration annotation for {@link <api-class>}s.
+ *      * @since <version>
+ *      */
+ *     @Retention(RetentionPolicy.SOURCE)
+ *     @Target({ElementType.TYPE, ElementType.METHOD})
+ *     @MimeLookupRegistrationMetaAnnotation(<attributes>)
+ *     public static @interface Registration {
+ *         /**
+ *          * Mime type to which should be the given provider registered.
+ *          */
+ *         public String   mimeType();
+ *         /**
+ *          * Position of the provider in the list of providers.
+ *          */
+ *         public int      position() default Integer.MAX_VALUE;
+ *         /**
+ *          * Superseded providers.
+ *          */
+ *         public String[] supersedes() default {};
+ *     }
+ * 
+ * + * @author Jan Lahoda + * @since 1.19 + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.ANNOTATION_TYPE) +public @interface MimeLookupRegistrationMetaAnnotation { + + /** + * Layer subfolder where should be the registration placed. + * Needed only if {@link Class2LayerFolder} is needed. + */ + public String subfolderName() default ""; + + /** + * Where should the handling processor be generated. + */ + public String targetPackage(); + + /** + * If the specific subfolder is used, specify the target type. + * Needed only if {@link Class2LayerFolder} is needed. + */ + public Class clazz() default Object.class; + +} diff --git a/editor.mimelookup/test/unit/src/org/netbeans/modules/editor/mimelookup/CreateRegistrationProcessorTest.java b/editor.mimelookup/test/unit/src/org/netbeans/modules/editor/mimelookup/CreateRegistrationProcessorTest.java new file mode 100644 --- /dev/null +++ b/editor.mimelookup/test/unit/src/org/netbeans/modules/editor/mimelookup/CreateRegistrationProcessorTest.java @@ -0,0 +1,94 @@ +/* + * 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.editor.mimelookup; + +import org.openide.util.test.AnnotationProcessorTestUtils; +import java.io.ByteArrayOutputStream; +import java.io.File; +import org.netbeans.junit.NbTestCase; +import org.netbeans.spi.editor.mimelookup.MimeLookupRegistrationMetaAnnotation; +import static org.junit.Assert.*; + +/** + * + * @author lahvac + */ +public class CreateRegistrationProcessorTest extends NbTestCase { + + public CreateRegistrationProcessorTest(String name) { + super(name); + } + + public void testErrorReporting() throws Exception { + clearWorkDir(); + File src = new File(getWorkDir(), "src"); + File dest = new File(getWorkDir(), "classes"); + String annotation = MimeLookupRegistrationMetaAnnotation.class.getName(); + + AnnotationProcessorTestUtils.makeSource(src, "test.Test", + "@" + annotation + "(targetPackage=\"test\")", + "public @interface Test {", + " public int mimeType();", + " public String position();", + " public int supersedes();", + " public String extra();", + "}"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(src, "Test", dest, null, baos)); + String errors = baos.toString(); + assertTrue(errors, errors.contains("Attribute mimeType does not have correct type. Expected: java.lang.String, found: int")); + assertTrue(errors, errors.contains("Attribute position does not have correct type. Expected: int, found: java.lang.String")); + assertTrue(errors, errors.contains("Attribute supersedes does not have correct type. Expected: java.lang.String[], found: int")); + assertTrue(errors, errors.contains("warning: Attribute extra not supported by MimeLookupRegistrationMetaAnnotation.")); + + AnnotationProcessorTestUtils.makeSource(src, "test.Test", + "@" + annotation + "(targetPackage=\"test\")", + "public @interface Test {", + "}"); + baos = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(src, "Test", dest, null, baos)); + errors = baos.toString(); + assertTrue(errors, errors.contains("The annotation must specify mimeType attribute with type java.lang.String")); + } + +} \ No newline at end of file