# HG changeset patch # Parent a35cb1f8277a6e77a20ef8c0a848ffa53fce443e #192750: @NbBundle.Messages. diff --git a/openide.filesystems/src/org/openide/filesystems/annotations/LayerBuilder.java b/openide.filesystems/src/org/openide/filesystems/annotations/LayerBuilder.java --- a/openide.filesystems/src/org/openide/filesystems/annotations/LayerBuilder.java +++ b/openide.filesystems/src/org/openide/filesystems/annotations/LayerBuilder.java @@ -67,6 +67,7 @@ import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic.Kind; import javax.tools.StandardLocation; +import org.openide.util.NbBundle.Messages; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; @@ -529,38 +530,54 @@ } bundle = ((PackageElement) referenceElement).getQualifiedName() + ".Bundle"; } - if (processingEnv != null) { - String resource = bundle.replace('.', '/') + ".properties"; - try { - InputStream is; - try { - is = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", resource).openInputStream(); - } catch (FileNotFoundException x) { // #181355 - try { - is = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", resource).openInputStream(); - } catch (IOException x2) { - throw x; - } - } - try { - Properties p = new Properties(); - p.load(is); - if (p.getProperty(key) == null) { - throw new LayerGenerationException("No key '" + key + "' found in " + resource, originatingElement); - } - } finally { - is.close(); - } - } catch (IOException x) { - throw new LayerGenerationException("Could not open " + resource + ": " + x, originatingElement); - } - } + verifyBundleKey(bundle, key, m.group(1) == null); bundlevalue(attr, bundle, key); } else { stringvalue(attr, label); } return this; } + private void verifyBundleKey(String bundle, String key, boolean samePackage) throws LayerGenerationException { + if (processingEnv == null) { + return; + } + if (samePackage) { + for (Element e = originatingElement; e != null; e = e.getEnclosingElement()) { + Messages m = e.getAnnotation(Messages.class); + if (m != null) { + for (String kv : m.value()) { + if (kv.startsWith(key + "=")) { + return; + } + } + } + } + } + String resource = bundle.replace('.', '/') + ".properties"; + try { + InputStream is; + try { + is = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", resource).openInputStream(); + } catch (FileNotFoundException x) { // #181355 + try { + is = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", resource).openInputStream(); + } catch (IOException x2) { + throw x; + } + } + try { + Properties p = new Properties(); + p.load(is); + if (p.getProperty(key) == null) { + throw new LayerGenerationException("No key '" + key + "' found in " + resource, originatingElement); + } + } finally { + is.close(); + } + } catch (IOException x) { + throw new LayerGenerationException("Could not open " + resource + ": " + x, originatingElement); + } + } /** * Adds an attribute which deserializes a Java value. diff --git a/openide.filesystems/test/unit/src/org/openide/filesystems/annotations/LayerBuilderTest.java b/openide.filesystems/test/unit/src/org/openide/filesystems/annotations/LayerBuilderTest.java --- a/openide.filesystems/test/unit/src/org/openide/filesystems/annotations/LayerBuilderTest.java +++ b/openide.filesystems/test/unit/src/org/openide/filesystems/annotations/LayerBuilderTest.java @@ -77,9 +77,8 @@ private Document doc; private LayerBuilder b; - @Override - protected void setUp() throws Exception { - super.setUp(); + protected @Override void setUp() throws Exception { + clearWorkDir(); doc = XMLUtil.createDocument("filesystem", null, null, null); b = new LayerBuilder(doc, null, null); assertEquals("", dump()); @@ -216,7 +215,6 @@ } public void testSourcePath() throws Exception { // #181355 - clearWorkDir(); File src = new File(getWorkDir(), "src"); AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(displayName=\"#label\") public class C {}"); File dest = new File(getWorkDir(), "dest"); @@ -229,6 +227,32 @@ clean(TestFileUtils.readFile(layer))); } + public void testMissingBundleError() throws Exception { + File src = new File(getWorkDir(), "src"); + AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(displayName=\"#nonexistent\") public class C {}"); + File dest = new File(getWorkDir(), "dest"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, err)); + assertTrue(err.toString(), err.toString().contains("p/Bundle.properties")); + } + + public void testMissingBundleKeyError() throws Exception { + File src = new File(getWorkDir(), "src"); + AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(displayName=\"#nonexistent\") public class C {}"); + TestFileUtils.writeFile(new File(src, "p/Bundle.properties"), "label=hello"); + File dest = new File(getWorkDir(), "dest"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, err)); + assertTrue(err.toString(), err.toString().contains("nonexistent")); + } + + public void testBundleKeyDefinedUsingMessages() throws Exception { + File src = new File(getWorkDir(), "src"); + AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(displayName=\"#k\") @org.openide.util.NbBundle.Messages(\"k=v\") public class C {}"); + File dest = new File(getWorkDir(), "dest"); + assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null)); + } + public @interface A {String displayName();} @ServiceProvider(service=Processor.class) diff --git a/openide.util/apichanges.xml b/openide.util/apichanges.xml --- a/openide.util/apichanges.xml +++ b/openide.util/apichanges.xml @@ -51,6 +51,21 @@ Actions API + + + Added @NbBundle.Messages + + + + + +

+ A new annotation makes it easier to produce localizable strings. +

+
+ + +
Supress logging of not important exceptions @@ -60,8 +75,8 @@

- New method Exceptions.attachSeverity allows - everyone to thrown an exception, which later will not be + New method Exceptions.attachSeverity allows + everyone to thrown an exception, which later will not be logged.

diff --git a/openide.util/manifest.mf b/openide.util/manifest.mf --- a/openide.util/manifest.mf +++ b/openide.util/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.util OpenIDE-Module-Localizing-Bundle: org/openide/util/Bundle.properties -OpenIDE-Module-Specification-Version: 8.9 +OpenIDE-Module-Specification-Version: 8.10 diff --git a/openide.util/src/org/netbeans/modules/openide/util/NbBundleProcessor.java b/openide.util/src/org/netbeans/modules/openide/util/NbBundleProcessor.java new file mode 100644 --- /dev/null +++ b/openide.util/src/org/netbeans/modules/openide/util/NbBundleProcessor.java @@ -0,0 +1,294 @@ +/* + * 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.openide.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.annotation.processing.AbstractProcessor; +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.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic.Kind; +import javax.tools.StandardLocation; +import org.openide.util.EditableProperties; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service = Processor.class) +@SupportedSourceVersion(SourceVersion.RELEASE_6) +public class NbBundleProcessor extends AbstractProcessor { + + public @Override Set getSupportedAnnotationTypes() { + return Collections.singleton(NbBundle.Messages.class.getCanonicalName()); + } + + public @Override boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + return false; + } + Map> pairs = new HashMap>(); + Map> identifiers = new HashMap>(); + Map> originatingElements = new HashMap>(); + Map> comments = new HashMap>(); + for (Element e : roundEnv.getElementsAnnotatedWith(NbBundle.Messages.class)) { + String pkg = findPackage(e); + Map pairsByPackage = pairs.get(pkg); + if (pairsByPackage == null) { + pairsByPackage = new HashMap(); + pairs.put(pkg, pairsByPackage); + } + Set identifiersByPackage = identifiers.get(pkg); + if (identifiersByPackage == null) { + identifiersByPackage = new HashSet(); + identifiers.put(pkg, identifiersByPackage); + } + List originatingElementsByPackage = originatingElements.get(pkg); + if (originatingElementsByPackage == null) { + originatingElementsByPackage = new ArrayList(); + originatingElements.put(pkg, originatingElementsByPackage); + } + Map commentsByPackage = comments.get(pkg); + if (commentsByPackage == null) { + commentsByPackage = new HashMap(); + comments.put(pkg, commentsByPackage); + } + List runningComments = new ArrayList(); + for (String keyValue : e.getAnnotation(NbBundle.Messages.class).value()) { + if (keyValue.startsWith("#")) { + runningComments.add(keyValue); + continue; + } + int i = keyValue.indexOf('='); + if (i == -1) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Bad key=value: " + keyValue, e); + continue; + } + String key = keyValue.substring(0, i); + if (key.isEmpty() || !key.equals(key.trim())) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Whitespace not permitted in key: " + keyValue, e); + continue; + } + if (!identifiersByPackage.add(toIdentifier(key))) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate key: " + key, e); + continue; + } + String value = keyValue.substring(i + 1); + pairsByPackage.put(key, value); + originatingElementsByPackage.add(e); + if (!runningComments.isEmpty()) { + commentsByPackage.put(key, runningComments.toArray(new String[runningComments.size()])); + runningComments.clear(); + } + } + if (!runningComments.isEmpty()) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Comments must precede keys", e); + } + } + for (Map.Entry> entry : pairs.entrySet()) { + String pkg = entry.getKey(); + Map keysAndValues = entry.getValue(); + Element[] elements = originatingElements.get(pkg).toArray(new Element[0]); + try { + EditableProperties p = new EditableProperties(true); + // Load any preexisting bundle so we can just add our keys. + try { + InputStream is = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, pkg, "Bundle.properties").openInputStream(); + try { + p.load(is); + } finally { + is.close(); + } + } catch (IOException x) { + // OK, not there + } + for (String key : p.keySet()) { + if (keysAndValues.containsKey(key)) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Key " + key + " is a duplicate of one from Bundle.properties", elements[0]); + } + } + // Also check class output for (1) incremental builds, (2) preexisting bundles from Maven projects. + try { + InputStream is = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, "Bundle.properties").openInputStream(); + try { + // do not use p.load(is) as the impl in EditableProperties does not currently handle duplicates properly + EditableProperties p2 = new EditableProperties(true); + p2.load(is); + p.putAll(p2); + } finally { + is.close(); + } + } catch (IOException x) { + // OK, not there + } + p.putAll(keysAndValues); + for (Map.Entry entry2 : comments.get(pkg).entrySet()) { + p.setComment(entry2.getKey(), entry2.getValue(), false); + } + OutputStream os = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, pkg, "Bundle.properties", elements).openOutputStream(); + try { + p.store(os); + } finally { + os.close(); + } + Map methods = new TreeMap(); + try { + Matcher m = Pattern.compile(" /[*][*]\r?\n(?: [*].+\r?\n)+ [*]/\r?\n static String (\\w+).+\r?\n .+\r?\n [}]\r?\n").matcher(processingEnv.getFiler().getResource(StandardLocation.SOURCE_OUTPUT, pkg, "Bundle.java").getCharContent(false)); + while (m.find()) { + methods.put(m.group(1), m.group()); + } + } catch (IOException x) { + // OK, not there + } + for (Map.Entry entry2 : keysAndValues.entrySet()) { + String key = entry2.getKey(); + String value = entry2.getValue(); + StringBuilder method = new StringBuilder(); + method.append(" /**\n"); + List params = new ArrayList(); + int i = 0; + while (value.contains("{" + i)) { + params.add("arg" + i++); + } + String[] commentLines = comments.get(pkg).get(key); + if (commentLines != null) { + for (String comment : commentLines) { + Matcher m = Pattern.compile("# [{](\\d+)[}] - (.+)").matcher(comment); + if (m.matches()) { + i = Integer.parseInt(m.group(1)); + String desc = m.group(2); + params.set(i, toIdentifier(desc)); + method.append(" * @param ").append(params.get(i)).append(" ").append(toJavadoc(desc)).append("\n"); + } + } + } + method.append(" * @return ").append(toJavadoc(value)).append("\n"); + method.append(" */\n"); + String name = toIdentifier(key); + method.append(" static String ").append(name).append("("); + boolean first = true; + for (String param : params) { + if (first) { + first = false; + } else { + method.append(", "); + } + method.append("Object ").append(param); + } + method.append(") {\n"); + method.append(" return org.openide.util.NbBundle.getMessage(Bundle.class, \"").append(key).append("\""); + for (String param : params) { + method.append(", ").append(param); + } + method.append(");\n"); + method.append(" }\n"); + methods.put(name, method.toString()); + } + String fqn = pkg + ".Bundle"; + Writer w = processingEnv.getFiler().createSourceFile(fqn, elements).openWriter(); + try { + PrintWriter pw = new PrintWriter(w); + pw.println("package " + pkg + ";"); + pw.println("/** Localizable strings for {@link " + pkg + "}. */"); + pw.println("class Bundle {"); + for (String method : methods.values()) { + pw.print(method); + } + pw.println(" private void Bundle() {}"); + pw.println("}"); + pw.flush(); + pw.close(); + } finally { + w.close(); + } + } catch (IOException x) { + processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate files: " + x, elements[0]); + } + } + return true; + } + + private String findPackage(Element e) { + switch (e.getKind()) { + case PACKAGE: + return ((PackageElement) e).getQualifiedName().toString(); + default: + return findPackage(e.getEnclosingElement()); + } + } + + private String toIdentifier(String key) { + if (Utilities.isJavaIdentifier(key)) { + return key; + } else { + String i = key.replaceAll("[^\\p{javaJavaIdentifierPart}]+", "_"); + if (Utilities.isJavaIdentifier(i)) { + return i; + } else { + return "_" + i; + } + } + } + + private String toJavadoc(String text) { + return text.replace("&", "&").replace("<", "<").replace("*/", "*/").replace("\n", "
").replace("@", "@"); + } + +} diff --git a/openide.util/src/org/openide/util/NbBundle.java b/openide.util/src/org/openide/util/NbBundle.java --- a/openide.util/src/org/openide/util/NbBundle.java +++ b/openide.util/src/org/openide/util/NbBundle.java @@ -46,6 +46,10 @@ import java.io.IOException; import java.io.InputStream; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.net.URL; @@ -81,6 +85,7 @@ * * will in German locale look for the key {@code Foo.displayName} in * {@code com/mycom/Bundle_de.properties} and then {@code com/mycom/Bundle.properties} (in that order). + * Usually however it is easiest to use {@link org.openide.util.NbBundle.Messages}. */ public class NbBundle extends Object { @@ -771,6 +776,68 @@ } /** + * Creates a helper class with static definitions of bundle keys. + *

+ * The generated class will be called {@code Bundle} and be in the same package. + * Each key is placed in a {@code Bundle.properties} file also in the same package, + * and the helper class gets a method with the same name as the key + * (converted to a valid Java identifier as needed) + * which loads the key from the (possibly now localized) bundle using {@link NbBundle#getMessage(Class, String)}. + * The method will have as many arguments (of type {@code Object}) as there are message format parameters. + *

+ *

It is an error to duplicate a key within a package, even if the duplicates are from different compilation units.

+ *

Example usage:

+ *
+     * package some.where;
+     * import org.openide.util.NbBundle.Messages;
+     * import static some.where.Bundle.*;
+     * import org.openide.DialogDisplayer;
+     * import org.openide.NotifyDescriptor;
+     * class Something {
+     *     @Messages({
+     *         "dialog.title=Bad File",
+     *         "# {0} - file path",
+     *         "dialog.message=The file {0} was invalid."
+     *     })
+     *     void showError(File f) {
+     *         NotifyDescriptor d = new NotifyDescriptor.Message(
+     *             dialog_message(f), NotifyDescriptor.ERROR_MESSAGE);
+     *         d.setTitle(dialog_title());
+     *         DialogDisplayer.getDefault().notify(d);
+     *     }
+     * }
+     * 
+ *

which generates during compilation {@code Bundle.java}:

+ *
+     * class Bundle {
+     *     static String dialog_title() {...}
+     *     static String dialog_message(Object file_path) {...}
+     * }
+     * 
+ *

and {@code Bundle.properties}:

+ *
+     * title=Bad File
+     * # {0} - file path
+     * message=The file {0} was invalid.
+     * 
+ * @since org.openide.util 8.10 + */ + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR}) + public @interface Messages { + /** + * List of key/value pairs. + * Each must be of the form {@code key=Some Value}. + * Anything is permitted in the value, including newlines. + * Unlike in a properties file, there should be no whitespace before the key or around the equals sign. + * Values containing {0} etc. are assumed to be message formats and so may need escapes for metacharacters such as {@code '}. + * A line may also be a comment if it starts with {@code #}, which may be useful for translators; + * it is recommended to use the format {@code # {0} - summary of param}. + */ + String[] value(); + } + + /** * Do not use. * @deprecated Useless. */ diff --git a/openide.util/test/unit/src/org/netbeans/modules/openide/util/NbBundleProcessorTest.java b/openide.util/test/unit/src/org/netbeans/modules/openide/util/NbBundleProcessorTest.java new file mode 100644 --- /dev/null +++ b/openide.util/test/unit/src/org/netbeans/modules/openide/util/NbBundleProcessorTest.java @@ -0,0 +1,205 @@ +/* + * 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.openide.util; + +import java.net.URL; +import java.io.File; +import java.io.ByteArrayOutputStream; +import java.net.URLClassLoader; +import org.openide.util.test.AnnotationProcessorTestUtils; +import org.netbeans.junit.NbTestCase; +import org.openide.util.NbBundle.Messages; +import org.openide.util.test.TestFileUtils; +import static org.netbeans.modules.openide.util.Bundle.*; + +@Messages("k3=value #3") +public class NbBundleProcessorTest extends NbTestCase { + + public NbBundleProcessorTest(String n) { + super(n); + } + + private File src; + private File dest; + protected @Override void setUp() throws Exception { + clearWorkDir(); + src = new File(getWorkDir(), "src"); + dest = new File(getWorkDir(), "classes"); + } + + @Messages({ + "k1=value #1", + "k2=value #2" + }) + public void testBasicUsage() throws Exception { + assertEquals("value #1", k1()); + assertEquals("value #2", k2()); + assertEquals("value #3", k3()); + } + + @Messages({ + "f1=problem with {0}", + "# {0} - input file", + "# {1} - pattern", + "f2={0} did not match {1}", + "LBL_BuildMainProjectAction_Name=&Build {0,choice,-1#Main Project|0#Project|1#Project ({1})|1<{0} Projects}" + }) + public void testMessageFormats() throws Exception { + assertEquals("problem with stuff", f1("stuff")); + assertEquals("1 did not match 2", f2(1, 2)); + assertEquals("&Build Main Project", LBL_BuildMainProjectAction_Name(-1, "whatever")); + assertEquals("&Build Project", LBL_BuildMainProjectAction_Name(0, "whatever")); + assertEquals("&Build Project (whatever)", LBL_BuildMainProjectAction_Name(1, "whatever")); + assertEquals("&Build 2 Projects", LBL_BuildMainProjectAction_Name(2, "whatever")); + } + + @Messages({ + "s1=Don't worry", + "s2=Don''t worry about {0}", + "s3=@camera Say \"cheese\"", + "s4=", + "s5=Operators: +-*/=", + "s6=One thing.\nAnd another." + }) + public void testSpecialCharacters() throws Exception { + assertEquals("Don't worry", s1()); + assertEquals("Don't worry about me", s2("me")); + assertEquals("@camera Say \"cheese\"", s3()); + assertEquals("", s4()); + assertEquals("Operators: +-*/=", s5()); + assertEquals("One thing.\nAnd another.", s6()); + } + + @Messages({ + "some key=some value", + "public=property", + "2+2=4" + }) + public void testNonIdentifierKeys() throws Exception { + assertEquals("some value", some_key()); + assertEquals("property", _public()); + assertEquals("4", _2_2()); + } + + public void testPackageKeys() throws Exception { + assertEquals("stuff", org.netbeans.modules.openide.util.Bundle.general()); + } + + public void testDupeErrorSimple() throws Exception { + AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages({\"k=v1\", \"k=v2\"})", "class C {}"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err)); + assertTrue(err.toString(), err.toString().contains("uplicate")); + } + + public void testDupeErrorByIdentifier() throws Exception { + AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages({\"k.=v1\", \"k,=v2\"})", "class C {}"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err)); + assertTrue(err.toString(), err.toString().contains("uplicate")); + } + + public void testDupeErrorAcrossClasses() throws Exception { + AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C1", "@org.openide.util.NbBundle.Messages({\"k=v\"})", "class C1 {}"); + AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C2", "@org.openide.util.NbBundle.Messages({\"k=v\"})", "class C2 {}"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err)); + assertTrue(err.toString(), err.toString().contains("uplicate")); + } + + public void testNoEqualsError() throws Exception { + AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages(\"whatever\")", "class C {}"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err)); + assertTrue(err.toString(), err.toString().contains("=")); + } + + public void testWhitespaceError() throws Exception { + AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages(\"key = value\")", "class C {}"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err)); + assertTrue(err.toString(), err.toString().contains("=")); + } + + public void testExistingBundle() throws Exception { + AnnotationProcessorTestUtils.makeSource(src, "p.C", "@org.openide.util.NbBundle.Messages(\"k=v\")", "class C {}"); + TestFileUtils.writeFile(new File(src, "p/Bundle.properties"), "# original comment\nold=stuff\n"); + assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null)); + assertEquals("k=v\n# original comment\nold=stuff\n", TestFileUtils.readFile(new File(dest, "p/Bundle.properties"))); + // Also check that we can recompile: + assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null)); + assertEquals("k=v\n# original comment\nold=stuff\n", TestFileUtils.readFile(new File(dest, "p/Bundle.properties"))); + } + + public void testDupeErrorWithExistingBundle() throws Exception { + AnnotationProcessorTestUtils.makeSource(src, "p.C", "@org.openide.util.NbBundle.Messages(\"k=v\")", "class C {}"); + TestFileUtils.writeFile(new File(src, "p/Bundle.properties"), "k=v\n"); + ByteArrayOutputStream err = new ByteArrayOutputStream(); + assertFalse(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, err)); + assertTrue(err.toString(), err.toString().contains("uplicate")); + } + + public void testIncrementalCompilation() throws Exception { + AnnotationProcessorTestUtils.makeSource(src, "p.C1", "@org.openide.util.NbBundle.Messages(\"k1=v1\")", "public class C1 {public @Override String toString() {return Bundle.k1();}}"); + AnnotationProcessorTestUtils.makeSource(src, "p.C2", "@org.openide.util.NbBundle.Messages(\"k2=v2\")", "public class C2 {public @Override String toString() {return Bundle.k2();}}"); + assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null)); + ClassLoader l = new URLClassLoader(new URL[] {dest.toURI().toURL()}); + assertEquals("v1", l.loadClass("p.C1").newInstance().toString()); + assertEquals("v2", l.loadClass("p.C2").newInstance().toString()); + AnnotationProcessorTestUtils.makeSource(src, "p.C1", "@org.openide.util.NbBundle.Messages(\"k1=v3\")", "public class C1 {public @Override String toString() {return Bundle.k1();}}"); + assertTrue(AnnotationProcessorTestUtils.runJavac(src, "C1.java", dest, null, null)); + l = new URLClassLoader(new URL[] {dest.toURI().toURL()}); + assertEquals("v3", l.loadClass("p.C1").newInstance().toString()); + assertEquals("v2", l.loadClass("p.C2").newInstance().toString()); + } + + public void testComments() throws Exception { + AnnotationProcessorTestUtils.makeSource(src, "p.C", "@org.openide.util.NbBundle.Messages({\"# Something skvělý to note.\", \"k=v\"})", "class C {}"); + assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null)); + assertEquals("# Something skv\\u011bl\\u00fd to note.\nk=v\n", TestFileUtils.readFile(new File(dest, "p/Bundle.properties"))); + // Also check that we can recompile: + assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null)); + assertEquals("# Something skv\\u011bl\\u00fd to note.\nk=v\n", TestFileUtils.readFile(new File(dest, "p/Bundle.properties"))); + } + +} diff --git a/openide.util/test/unit/src/org/netbeans/modules/openide/util/package-info.java b/openide.util/test/unit/src/org/netbeans/modules/openide/util/package-info.java new file mode 100644 --- /dev/null +++ b/openide.util/test/unit/src/org/netbeans/modules/openide/util/package-info.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +@Messages("general=stuff") +package org.netbeans.modules.openide.util; +import org.openide.util.NbBundle.Messages;