LayerGenerationException
has new constructors+ Code using the constructors not specifying an annotation should now do so. +
+
+ LayerGenerationException
has new constructors making it easier to specify
+ the particular annotation responsible for a problem.
+ Some methods in LayerBuilder
have new overloads to take advantage of it.
+
One can convert files in SystemFileSystem to Object with - a + a single utility method.
--- a/openide.filesystems/manifest.mf +++ a/openide.filesystems/manifest.mf @@ -2,5 +2,5 @@ OpenIDE-Module: org.openide.filesystems OpenIDE-Module-Localizing-Bundle: org/openide/filesystems/Bundle.properties OpenIDE-Module-Layer: org/openide/filesystems/resources/layer.xml -OpenIDE-Module-Specification-Version: 7.49 +OpenIDE-Module-Specification-Version: 7.50 --- a/openide.filesystems/src/org/openide/filesystems/annotations/LayerBuilder.java +++ a/openide.filesystems/src/org/openide/filesystems/annotations/LayerBuilder.java @@ -45,7 +45,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.ObjectOutputStream; +import java.lang.annotation.Annotation; import java.net.URI; import java.util.Arrays; import java.util.LinkedHashMap; @@ -146,8 +146,27 @@ * {@linkplain TypeElement class} or {@linkplain ExecutableElement method} * @throws LayerGenerationException if the associated element would not be loadable as an instance of the specified type */ - public File instanceFile(String path, String name, Class type) throws IllegalArgumentException, LayerGenerationException { - String[] clazzOrMethod = instantiableClassOrMethod(type); + public File instanceFile(String path, String name, Class> type) throws IllegalArgumentException, LayerGenerationException { + return instanceFile(path, name, type, null, null); + } + /** + * Generates an instance file whose {@code InstanceCookie} would load the associated class or method. + * Useful for {@link LayerGeneratingProcessor}s which define layer fragments which instantiate Java objects from the annotated code. + *While you can pick a specific instance file name, if possible you should pass null for {@code name} + * as using the generated name will help avoid accidental name collisions between annotations. + * @param path path to folder of instance file, e.g. {@code "Menu/File"} + * @param name instance file basename, e.g. {@code "my-menu-Item"}, or null to pick a name according to the element + * @param type a type to which the instance ought to be assignable, or null to skip this check + * @param annotation as in {@link LayerGenerationException#LayerGenerationException(String,Element,ProcessingEnvironment,Annotation,String)} + * @param annotationMethod as in {@link LayerGenerationException#LayerGenerationException(String,Element,ProcessingEnvironment,Annotation,String)} + * @return an instance file (call {@link File#write} to finalize) + * @throws IllegalArgumentException if the builder is not associated with exactly one + * {@linkplain TypeElement class} or {@linkplain ExecutableElement method} + * @throws LayerGenerationException if the associated element would not be loadable as an instance of the specified type + * @since 7.50 + */ + public File instanceFile(String path, String name, Class> type, Annotation annotation, String annotationMethod) throws IllegalArgumentException, LayerGenerationException { + String[] clazzOrMethod = instantiableClassOrMethod(type, annotation, annotationMethod); String clazz = clazzOrMethod[0]; String method = clazzOrMethod[1]; String basename; @@ -185,7 +204,28 @@ * @since org.openide.filesystems 7.27 */ public File instanceFile(String path, String name) throws IllegalArgumentException, LayerGenerationException { - String[] clazzOrMethod = instantiableClassOrMethod(null); + return instanceFile(path, name, null, null); + } + /** + * Generates an instance file that is not initialized with an instance. + * Useful for {@link LayerGeneratingProcessor}s which define layer fragments + * which indirectly instantiate Java objects from the annotated code via a generic factory method. + * Invoke the factory using {@link File#methodvalue} on {@code instanceCreate} + * and configure it with a {@link File#instanceAttribute} appropriate to the factory. + *
While you can pick a specific instance file name, if possible you should pass null for {@code name} + * as using the generated name will help avoid accidental name collisions between annotations. + * @param path path to folder of instance file, e.g. {@code "Menu/File"} + * @param name instance file basename, e.g. {@code "my-menu-Item"}, or null to pick a name according to the element + * @param annotation as in {@link LayerGenerationException#LayerGenerationException(String,Element,ProcessingEnvironment,Annotation,String)} + * @param annotationMethod as in {@link LayerGenerationException#LayerGenerationException(String,Element,ProcessingEnvironment,Annotation,String)} + * @return an instance file (call {@link File#write} to finalize) + * @throws IllegalArgumentException if the builder is not associated with exactly one + * {@linkplain TypeElement class} or {@linkplain ExecutableElement method} + * @throws LayerGenerationException if the associated element would not be loadable as an instance + * @since org.openide.filesystems 7.50 + */ + public File instanceFile(String path, String name, Annotation annotation, String annotationMethod) throws IllegalArgumentException, LayerGenerationException { + String[] clazzOrMethod = instantiableClassOrMethod(null, annotation, annotationMethod); String clazz = clazzOrMethod[0]; String method = clazzOrMethod[1]; String basename; @@ -200,7 +240,7 @@ return file(path + "/" + basename + ".instance"); } - private String[] instantiableClassOrMethod(Class type) throws IllegalArgumentException, LayerGenerationException { + private String[] instantiableClassOrMethod(Class> type, Annotation annotation, String annotationMethod) throws IllegalArgumentException, LayerGenerationException { if (originatingElement == null) { throw new IllegalArgumentException("Only applicable to builders with exactly one associated element"); } @@ -212,7 +252,7 @@ case CLASS: { String clazz = processingEnv.getElementUtils().getBinaryName((TypeElement) originatingElement).toString(); if (originatingElement.getModifiers().contains(Modifier.ABSTRACT)) { - throw new LayerGenerationException(clazz + " must not be abstract", originatingElement); + throw new LayerGenerationException(clazz + " must not be abstract", originatingElement, processingEnv, annotation, annotationMethod); } { boolean hasDefaultCtor = false; @@ -223,14 +263,14 @@ } } if (!hasDefaultCtor) { - throw new LayerGenerationException(clazz + " must have a no-argument constructor", originatingElement); + throw new LayerGenerationException(clazz + " must have a no-argument constructor", originatingElement, processingEnv, annotation, annotationMethod); } } if (typeMirror != null && !processingEnv.getTypeUtils().isAssignable(originatingElement.asType(), typeMirror)) { - throw new LayerGenerationException(clazz + " is not assignable to " + typeMirror, originatingElement); + throw new LayerGenerationException(clazz + " is not assignable to " + typeMirror, originatingElement, processingEnv, annotation, annotationMethod); } if (!originatingElement.getModifiers().contains(Modifier.PUBLIC)) { - throw new LayerGenerationException(clazz + " is not public", originatingElement); + throw new LayerGenerationException(clazz + " is not public", originatingElement, processingEnv, annotation, annotationMethod); } return new String[] {clazz, null}; } @@ -238,13 +278,13 @@ String clazz = processingEnv.getElementUtils().getBinaryName((TypeElement) originatingElement.getEnclosingElement()).toString(); String method = originatingElement.getSimpleName().toString(); if (!originatingElement.getModifiers().contains(Modifier.STATIC)) { - throw new LayerGenerationException(clazz + "." + method + " must be static", originatingElement); + throw new LayerGenerationException(clazz + "." + method + " must be static", originatingElement, processingEnv, annotation, annotationMethod); } if (!((ExecutableElement) originatingElement).getParameters().isEmpty()) { - throw new LayerGenerationException(clazz + "." + method + " must not take arguments", originatingElement); + throw new LayerGenerationException(clazz + "." + method + " must not take arguments", originatingElement, processingEnv, annotation, annotationMethod); } if (typeMirror != null && !processingEnv.getTypeUtils().isAssignable(((ExecutableElement) originatingElement).getReturnType(), typeMirror)) { - throw new LayerGenerationException(clazz + "." + method + " is not assignable to " + typeMirror, originatingElement); + throw new LayerGenerationException(clazz + "." + method + " is not assignable to " + typeMirror, originatingElement, processingEnv, annotation, annotationMethod); } return new String[] {clazz, method}; } @@ -485,8 +525,23 @@ * @throws IllegalArgumentException if the associated element is not a {@linkplain TypeElement class} or {@linkplain ExecutableElement method} * @throws LayerGenerationException if the associated element would not be loadable as an instance of the specified type */ - public File instanceAttribute(String attr, Class type) throws IllegalArgumentException, LayerGenerationException { - String[] clazzOrMethod = instantiableClassOrMethod(type); + public File instanceAttribute(String attr, Class> type) throws IllegalArgumentException, LayerGenerationException { + return instanceAttribute(attr, type, null, null); + } + /** + * Adds an attribute to load the associated class or method. + * Useful for {@link LayerGeneratingProcessor}s which define layer fragments which instantiate Java objects from the annotated code. + * @param attr the attribute name + * @param type a type to which the instance ought to be assignable, or null to skip this check + * @param annotation as in {@link LayerGenerationException#LayerGenerationException(String,Element,ProcessingEnvironment,Annotation,String)} + * @param annotationMethod as in {@link LayerGenerationException#LayerGenerationException(String,Element,ProcessingEnvironment,Annotation,String)} + * @return this builder + * @throws IllegalArgumentException if the associated element is not a {@linkplain TypeElement class} or {@linkplain ExecutableElement method} + * @throws LayerGenerationException if the associated element would not be loadable as an instance of the specified type + * @since 7.50 + */ + public File instanceAttribute(String attr, Class> type, Annotation annotation, String annotationMethod) throws IllegalArgumentException, LayerGenerationException { + String[] clazzOrMethod = instantiableClassOrMethod(type, annotation, annotationMethod); if (clazzOrMethod[1] == null) { newvalue(attr, clazzOrMethod[0]); } else { @@ -518,6 +573,22 @@ * @throws LayerGenerationException if a bundle key is requested but it cannot be found in sources */ public File bundlevalue(String attr, String label) throws LayerGenerationException { + return bundlevalue(attr, label, null, null); + } + /** + * Adds an attribute for a possibly localized string. + * @param attr the attribute name + * @param label either a general string to store as is, or a resource bundle reference + * such as {@code "my.module.Bundle#some_key"}, + * or just {@code "#some_key"} to load from a {@code "Bundle"} + * in the same package as the element associated with this builder (if exactly one) + * @param annotation as in {@link LayerGenerationException#LayerGenerationException(String,Element,ProcessingEnvironment,Annotation,String)} + * @param annotationMethod as in {@link LayerGenerationException#LayerGenerationException(String,Element,ProcessingEnvironment,Annotation,String)} + * @return this builder + * @throws LayerGenerationException if a bundle key is requested but it cannot be found in sources + * @since 7.50 + */ + public File bundlevalue(String attr, String label, Annotation annotation, String annotationMethod) throws LayerGenerationException { String javaIdentifier = "(?:\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)"; Matcher m = Pattern.compile("((?:" + javaIdentifier + "\\.)+[^\\s.#]+)?#(\\S*)").matcher(label); if (m.matches()) { @@ -533,14 +604,14 @@ } bundle = ((PackageElement) referenceElement).getQualifiedName() + ".Bundle"; } - verifyBundleKey(bundle, key, m.group(1) == null); + verifyBundleKey(bundle, key, m.group(1) == null, annotation, annotationMethod); bundlevalue(attr, bundle, key); } else { stringvalue(attr, label); } return this; } - private void verifyBundleKey(String bundle, String key, boolean samePackage) throws LayerGenerationException { + private void verifyBundleKey(String bundle, String key, boolean samePackage, Annotation annotation, String annotationMethod) throws LayerGenerationException { if (processingEnv == null) { return; } @@ -572,13 +643,13 @@ Properties p = new Properties(); p.load(is); if (p.getProperty(key) == null) { - throw new LayerGenerationException("No key '" + key + "' found in " + resource, originatingElement); + throw new LayerGenerationException("No key '" + key + "' found in " + resource, originatingElement, processingEnv, annotation, annotationMethod); } } finally { is.close(); } } catch (IOException x) { - throw new LayerGenerationException("Could not open " + resource + ": " + x, originatingElement); + throw new LayerGenerationException("Could not open " + resource + ": " + x, originatingElement, processingEnv, annotation, annotationMethod); } } --- a/openide.filesystems/src/org/openide/filesystems/annotations/LayerGenerationException.java +++ a/openide.filesystems/src/org/openide/filesystems/annotations/LayerGenerationException.java @@ -42,13 +42,19 @@ package org.openide.filesystems.annotations; +import java.lang.annotation.Annotation; +import java.util.Map; import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; 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.TypeElement; /** * Exception thrown when a layer entry cannot be generated due to erroneous sources. + * @see LayerGeneratingProcessor * @since org.openide.filesystems 7.15 */ public class LayerGenerationException extends Exception { @@ -63,7 +69,7 @@ * @see Messager#printMessage(javax.tools.Diagnostic.Kind, CharSequence) */ public LayerGenerationException(String message) { - this(message, null, null, null); + this(message, (Element) null, (AnnotationMirror) null, (AnnotationValue) null); } /** @@ -73,7 +79,7 @@ * @see Messager#printMessage(javax.tools.Diagnostic.Kind, CharSequence, Element) */ public LayerGenerationException(String message, Element erroneousElement) { - this(message, erroneousElement, null, null); + this(message, erroneousElement, (AnnotationMirror) null, (AnnotationValue) null); } /** @@ -84,7 +90,7 @@ * @see Messager#printMessage(javax.tools.Diagnostic.Kind, CharSequence, Element, AnnotationMirror) */ public LayerGenerationException(String message, Element erroneousElement, AnnotationMirror erroneousAnnotation) { - this(message, erroneousElement, erroneousAnnotation, null); + this(message, erroneousElement, erroneousAnnotation, (AnnotationValue) null); } /** @@ -102,4 +108,84 @@ this.erroneousAnnotationValue = erroneousAnnotationValue; } + /** + * An exception with an associated annotation. + * Convenience constructor which locates an annotation on the erroneous element for you. + * @param message a detail message which could be reported to the user + * @param erroneousElement the associated element + * @param processingEnv the processing environment passed to the processor + * @param erroneousAnnotation the reflected annotation on the element (may be null as a convenience) + * @see Messager#printMessage(javax.tools.Diagnostic.Kind, CharSequence, Element, AnnotationMirror) + * @since 7.50 + */ + public LayerGenerationException(String message, Element erroneousElement, ProcessingEnvironment processingEnv, + Annotation erroneousAnnotation) { + this(message, erroneousElement, processingEnv, erroneousAnnotation, (String) null); + } + + /** + * An exception with an associated annotation value. + * Convenience constructor which locates an annotation and its value on the erroneous element for you. + * @param message a detail message which could be reported to the user + * @param erroneousElement the associated element + * @param processingEnv the processing environment passed to the processor + * @param erroneousAnnotation the reflected annotation on the element (may be null as a convenience) + * @param erroneousAnnotationMethod the name of a method in that annotation (may be null) + * @see Messager#printMessage(javax.tools.Diagnostic.Kind, CharSequence, Element, AnnotationMirror, AnnotationValue) + * @since 7.50 + */ + public LayerGenerationException(String message, Element erroneousElement, ProcessingEnvironment processingEnv, + Annotation erroneousAnnotation, String erroneousAnnotationMethod) { + super(message); + this.erroneousElement = erroneousElement; + if (erroneousAnnotation != null) { + Class extends Annotation> clazz = null; + Class> implClass = erroneousAnnotation.getClass(); + for (Class> xface : implClass.getInterfaces()) { + if (xface.isAnnotation()) { + if (clazz == null) { + clazz = xface.asSubclass(Annotation.class); + } else { + throw new IllegalArgumentException(">1 annotation implemented by " + implClass.getName()); + } + } + } + if (clazz == null) { + throw new IllegalArgumentException("no annotation implemented by " + implClass.getName()); + } + if (erroneousAnnotationMethod != null) { + try { + clazz.getMethod(erroneousAnnotationMethod); + } catch (NoSuchMethodException x) { + throw new IllegalArgumentException("No such method " + erroneousAnnotationMethod + " in " + erroneousAnnotation); + } catch (SecurityException x) {/* ignore? */} + } + this.erroneousAnnotation = findAnnotationMirror(erroneousElement, processingEnv, clazz); + this.erroneousAnnotationValue = this.erroneousAnnotation != null && erroneousAnnotationMethod != null ? + findAnnotationValue(this.erroneousAnnotation, erroneousAnnotationMethod) : null; + } else { + this.erroneousAnnotation = null; + this.erroneousAnnotationValue = null; + } + } + + private static AnnotationMirror findAnnotationMirror(Element element, ProcessingEnvironment processingEnv, Class extends Annotation> annotation) { + for (AnnotationMirror ann : element.getAnnotationMirrors()) { + if (processingEnv.getElementUtils().getBinaryName((TypeElement) ann.getAnnotationType().asElement()). + contentEquals(annotation.getName())) { + return ann; + } + } + return null; + } + + private AnnotationValue findAnnotationValue(AnnotationMirror annotation, String name) { + for (Map.Entry extends ExecutableElement,? extends AnnotationValue> entry : annotation.getElementValues().entrySet()) { + if (entry.getKey().getSimpleName().contentEquals(name)) { + return entry.getValue(); + } + } + return null; + } + } --- a/openide.filesystems/test/unit/src/org/openide/filesystems/annotations/LayerGenerationExceptionTest.java +++ a/openide.filesystems/test/unit/src/org/openide/filesystems/annotations/LayerGenerationExceptionTest.java @@ -0,0 +1,126 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + *
+ * Copyright 2011 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 2011 Sun Microsystems, Inc. + */ + +package org.openide.filesystems.annotations; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic.Kind; +import org.netbeans.junit.NbTestCase; +import org.openide.util.lookup.ServiceProvider; +import org.openide.util.test.AnnotationProcessorTestUtils; + +public class LayerGenerationExceptionTest extends NbTestCase { + + public LayerGenerationExceptionTest(String name) { + super(name); + } + + public void testFindAnnotationMirror() throws Exception { + File src = new File(getWorkDir(), "src"); + AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(attr1=\"one\", attr2=\"two\") public class C {}"); + File dest = new File(getWorkDir(), "dest"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, err)); + assertTrue(err.toString(), err.toString().contains("p.C two")); + } + + /* XXX not yet implemented: + public void testFindAnnotationMirrorNested() throws Exception { + File src = new File(getWorkDir(), "src"); + AnnotationProcessorTestUtils.makeSource(src, "p.C", + "@" + AS.class.getCanonicalName() + "({", + "@" + A.class.getCanonicalName() + "(attr1=\"one\", attr2=\"two\"),", + "@" + A.class.getCanonicalName() + "(attr1=\"three\", attr2=\"four\")", + "})", + "public class C {}"); + File dest = new File(getWorkDir(), "dest"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + boolean r = AnnotationProcessorTestUtils.runJavac(src, null, dest, null, err); + String out = err.toString(); + assertTrue(out, r); + assertTrue(out,out.contains("p.C two")); + assertTrue(out,out.contains("p.C four")); + } + */ + + public @interface A { + String attr1(); + String attr2(); + } + + public @interface AS { + A[] value(); + } + + @ServiceProvider(service=Processor.class) + @SupportedSourceVersion(SourceVersion.RELEASE_6) + public static class AP extends LayerGeneratingProcessor { + public @Override Set