This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 192750
Collapse All | Expand All

(-)a/openide.filesystems/src/org/openide/filesystems/annotations/LayerBuilder.java (-26 / +43 lines)
Lines 67-72 Link Here
67
import javax.lang.model.util.ElementFilter;
67
import javax.lang.model.util.ElementFilter;
68
import javax.tools.Diagnostic.Kind;
68
import javax.tools.Diagnostic.Kind;
69
import javax.tools.StandardLocation;
69
import javax.tools.StandardLocation;
70
import org.openide.util.NbBundle.Messages;
70
import org.w3c.dom.Document;
71
import org.w3c.dom.Document;
71
import org.w3c.dom.Node;
72
import org.w3c.dom.Node;
72
import org.w3c.dom.NodeList;
73
import org.w3c.dom.NodeList;
Lines 529-566 Link Here
529
                    }
530
                    }
530
                    bundle = ((PackageElement) referenceElement).getQualifiedName() + ".Bundle";
531
                    bundle = ((PackageElement) referenceElement).getQualifiedName() + ".Bundle";
531
                }
532
                }
532
                if (processingEnv != null) {
533
                verifyBundleKey(bundle, key, m.group(1) == null);
533
                    String resource = bundle.replace('.', '/') + ".properties";
534
                    try {
535
                        InputStream is;
536
                        try {
537
                            is = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", resource).openInputStream();
538
                        } catch (FileNotFoundException x) { // #181355
539
                            try {
540
                                is = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", resource).openInputStream();
541
                            } catch (IOException x2) {
542
                                throw x;
543
                            }
544
                        }
545
                        try {
546
                            Properties p = new Properties();
547
                            p.load(is);
548
                            if (p.getProperty(key) == null) {
549
                                throw new LayerGenerationException("No key '" + key + "' found in " + resource, originatingElement);
550
                            }
551
                        } finally {
552
                            is.close();
553
                        }
554
                    } catch (IOException x) {
555
                        throw new LayerGenerationException("Could not open " + resource + ": " + x, originatingElement);
556
                    }
557
                }
558
                bundlevalue(attr, bundle, key);
534
                bundlevalue(attr, bundle, key);
559
            } else {
535
            } else {
560
                stringvalue(attr, label);
536
                stringvalue(attr, label);
561
            }
537
            }
562
            return this;
538
            return this;
563
        }
539
        }
540
        private void verifyBundleKey(String bundle, String key, boolean samePackage) throws LayerGenerationException {
541
            if (processingEnv == null) {
542
                return;
543
            }
544
            if (samePackage) {
545
                for (Element e = originatingElement; e != null; e = e.getEnclosingElement()) {
546
                    Messages m = e.getAnnotation(Messages.class);
547
                    if (m != null) {
548
                        for (String kv : m.value()) {
549
                            if (kv.startsWith(key + "=")) {
550
                                return;
551
                            }
552
                        }
553
                    }
554
                }
555
            }
556
            String resource = bundle.replace('.', '/') + ".properties";
557
            try {
558
                InputStream is;
559
                try {
560
                    is = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, "", resource).openInputStream();
561
                } catch (FileNotFoundException x) { // #181355
562
                    try {
563
                        is = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", resource).openInputStream();
564
                    } catch (IOException x2) {
565
                        throw x;
566
                    }
567
                }
568
                try {
569
                    Properties p = new Properties();
570
                    p.load(is);
571
                    if (p.getProperty(key) == null) {
572
                        throw new LayerGenerationException("No key '" + key + "' found in " + resource, originatingElement);
573
                    }
574
                } finally {
575
                    is.close();
576
                }
577
            } catch (IOException x) {
578
                throw new LayerGenerationException("Could not open " + resource + ": " + x, originatingElement);
579
            }
580
        }
564
581
565
        /**
582
        /**
566
         * Adds an attribute which deserializes a Java value.
583
         * Adds an attribute which deserializes a Java value.
(-)a/openide.filesystems/test/unit/src/org/openide/filesystems/annotations/LayerBuilderTest.java (-4 / +28 lines)
Lines 77-85 Link Here
77
    private Document doc;
77
    private Document doc;
78
    private LayerBuilder b;
78
    private LayerBuilder b;
79
79
80
    @Override
80
    protected @Override void setUp() throws Exception {
81
    protected void setUp() throws Exception {
81
        clearWorkDir();
82
        super.setUp();
83
        doc = XMLUtil.createDocument("filesystem", null, null, null);
82
        doc = XMLUtil.createDocument("filesystem", null, null, null);
84
        b = new LayerBuilder(doc, null, null);
83
        b = new LayerBuilder(doc, null, null);
85
        assertEquals("<filesystem/>", dump());
84
        assertEquals("<filesystem/>", dump());
Lines 216-222 Link Here
216
    }
215
    }
217
216
218
    public void testSourcePath() throws Exception { // #181355
217
    public void testSourcePath() throws Exception { // #181355
219
        clearWorkDir();
220
        File src = new File(getWorkDir(), "src");
218
        File src = new File(getWorkDir(), "src");
221
        AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(displayName=\"#label\") public class C {}");
219
        AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(displayName=\"#label\") public class C {}");
222
        File dest = new File(getWorkDir(), "dest");
220
        File dest = new File(getWorkDir(), "dest");
Lines 229-234 Link Here
229
                clean(TestFileUtils.readFile(layer)));
227
                clean(TestFileUtils.readFile(layer)));
230
    }
228
    }
231
229
230
    public void testMissingBundleError() throws Exception {
231
        File src = new File(getWorkDir(), "src");
232
        AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(displayName=\"#nonexistent\") public class C {}");
233
        File dest = new File(getWorkDir(), "dest");
234
        ByteArrayOutputStream err = new ByteArrayOutputStream();
235
        assertFalse(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, err));
236
        assertTrue(err.toString(), err.toString().contains("p/Bundle.properties"));
237
    }
238
239
    public void testMissingBundleKeyError() throws Exception {
240
        File src = new File(getWorkDir(), "src");
241
        AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(displayName=\"#nonexistent\") public class C {}");
242
        TestFileUtils.writeFile(new File(src, "p/Bundle.properties"), "label=hello");
243
        File dest = new File(getWorkDir(), "dest");
244
        ByteArrayOutputStream err = new ByteArrayOutputStream();
245
        assertFalse(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, err));
246
        assertTrue(err.toString(), err.toString().contains("nonexistent"));
247
    }
248
249
    public void testBundleKeyDefinedUsingMessages() throws Exception {
250
        File src = new File(getWorkDir(), "src");
251
        AnnotationProcessorTestUtils.makeSource(src, "p.C", "@" + A.class.getCanonicalName() + "(displayName=\"#k\") @org.openide.util.NbBundle.Messages(\"k=v\") public class C {}");
252
        File dest = new File(getWorkDir(), "dest");
253
        assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null));
254
    }
255
232
    public @interface A {String displayName();}
256
    public @interface A {String displayName();}
233
257
234
    @ServiceProvider(service=Processor.class)
258
    @ServiceProvider(service=Processor.class)
(-)a/openide.util/apichanges.xml (-2 / +17 lines)
Lines 51-56 Link Here
51
    <apidef name="actions">Actions API</apidef>
51
    <apidef name="actions">Actions API</apidef>
52
</apidefs>
52
</apidefs>
53
<changes>
53
<changes>
54
    <change id="NbBundle.Messages">
55
        <api name="util"/>
56
        <summary>Added <code>@NbBundle.Messages</code></summary>
57
        <version major="8" minor="10"/>
58
        <date day="8" month="12" year="2010"/>
59
        <author login="jglick"/>
60
        <compatibility addition="yes"/>
61
        <description>
62
            <p>
63
                A new annotation makes it easier to produce localizable strings.
64
            </p>
65
        </description>
66
        <class package="org.openide.util" name="NbBundle"/>
67
        <issue number="192750"/>
68
    </change>
54
    <change id="Exceptions.attachSeverity">
69
    <change id="Exceptions.attachSeverity">
55
        <api name="util"/>
70
        <api name="util"/>
56
        <summary>Supress logging of not important exceptions</summary>
71
        <summary>Supress logging of not important exceptions</summary>
Lines 60-67 Link Here
60
        <compatibility addition="yes"/>
75
        <compatibility addition="yes"/>
61
        <description>
76
        <description>
62
            <p>
77
            <p>
63
                New method <code>Exceptions.attachSeverity</code> allows 
78
                New method <code>Exceptions.attachSeverity</code> allows
64
                everyone to thrown an exception, which later will not be 
79
                everyone to thrown an exception, which later will not be
65
                logged.
80
                logged.
66
            </p>
81
            </p>
67
        </description>
82
        </description>
(-)a/openide.util/manifest.mf (-1 / +1 lines)
Lines 1-5 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.openide.util
2
OpenIDE-Module: org.openide.util
3
OpenIDE-Module-Localizing-Bundle: org/openide/util/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/openide/util/Bundle.properties
4
OpenIDE-Module-Specification-Version: 8.9
4
OpenIDE-Module-Specification-Version: 8.10
5
5
(-)a/openide.util/src/org/netbeans/modules/openide/util/NbBundleProcessor.java (+294 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2010 Sun Microsystems, Inc.
41
 */
42
43
package org.netbeans.modules.openide.util;
44
45
import java.io.IOException;
46
import java.io.InputStream;
47
import java.io.OutputStream;
48
import java.io.PrintWriter;
49
import java.io.Writer;
50
import java.util.ArrayList;
51
import java.util.Collections;
52
import java.util.HashMap;
53
import java.util.HashSet;
54
import java.util.List;
55
import java.util.Map;
56
import java.util.Set;
57
import java.util.TreeMap;
58
import java.util.regex.Matcher;
59
import java.util.regex.Pattern;
60
import javax.annotation.processing.AbstractProcessor;
61
import javax.annotation.processing.Processor;
62
import javax.annotation.processing.RoundEnvironment;
63
import javax.annotation.processing.SupportedSourceVersion;
64
import javax.lang.model.SourceVersion;
65
import javax.lang.model.element.Element;
66
import javax.lang.model.element.PackageElement;
67
import javax.lang.model.element.TypeElement;
68
import javax.tools.Diagnostic.Kind;
69
import javax.tools.StandardLocation;
70
import org.openide.util.EditableProperties;
71
import org.openide.util.NbBundle;
72
import org.openide.util.Utilities;
73
import org.openide.util.lookup.ServiceProvider;
74
75
@ServiceProvider(service = Processor.class)
76
@SupportedSourceVersion(SourceVersion.RELEASE_6)
77
public class NbBundleProcessor extends AbstractProcessor {
78
79
    public @Override Set<String> getSupportedAnnotationTypes() {
80
        return Collections.singleton(NbBundle.Messages.class.getCanonicalName());
81
    }
82
83
    public @Override boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
84
        if (roundEnv.processingOver()) {
85
            return false;
86
        }
87
        Map</*package*/String,Map</*key*/String,/*value*/String>> pairs = new HashMap<String,Map<String,String>>();
88
        Map</*package*/String,Set</*identifier*/String>> identifiers = new HashMap<String,Set<String>>();
89
        Map</*package*/String,List<Element>> originatingElements = new HashMap<String,List<Element>>();
90
        Map</*package*/String,Map</*key*/String,/*line*/String[]>> comments = new HashMap<String,Map<String,String[]>>();
91
        for (Element e : roundEnv.getElementsAnnotatedWith(NbBundle.Messages.class)) {
92
            String pkg = findPackage(e);
93
            Map<String, String> pairsByPackage = pairs.get(pkg);
94
            if (pairsByPackage == null) {
95
                pairsByPackage = new HashMap<String, String>();
96
                pairs.put(pkg, pairsByPackage);
97
            }
98
            Set<String> identifiersByPackage = identifiers.get(pkg);
99
            if (identifiersByPackage == null) {
100
                identifiersByPackage = new HashSet<String>();
101
                identifiers.put(pkg, identifiersByPackage);
102
            }
103
            List<Element> originatingElementsByPackage = originatingElements.get(pkg);
104
            if (originatingElementsByPackage == null) {
105
                originatingElementsByPackage = new ArrayList<Element>();
106
                originatingElements.put(pkg, originatingElementsByPackage);
107
            }
108
            Map<String,String[]> commentsByPackage = comments.get(pkg);
109
            if (commentsByPackage == null) {
110
                commentsByPackage = new HashMap<String,String[]>();
111
                comments.put(pkg, commentsByPackage);
112
            }
113
            List<String> runningComments = new ArrayList<String>();
114
            for (String keyValue : e.getAnnotation(NbBundle.Messages.class).value()) {
115
                if (keyValue.startsWith("#")) {
116
                    runningComments.add(keyValue);
117
                    continue;
118
                }
119
                int i = keyValue.indexOf('=');
120
                if (i == -1) {
121
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Bad key=value: " + keyValue, e);
122
                    continue;
123
                }
124
                String key = keyValue.substring(0, i);
125
                if (key.isEmpty() || !key.equals(key.trim())) {
126
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Whitespace not permitted in key: " + keyValue, e);
127
                    continue;
128
                }
129
                if (!identifiersByPackage.add(toIdentifier(key))) {
130
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate key: " + key, e);
131
                    continue;
132
                }
133
                String value = keyValue.substring(i + 1);
134
                pairsByPackage.put(key, value);
135
                originatingElementsByPackage.add(e);
136
                if (!runningComments.isEmpty()) {
137
                    commentsByPackage.put(key, runningComments.toArray(new String[runningComments.size()]));
138
                    runningComments.clear();
139
                }
140
            }
141
            if (!runningComments.isEmpty()) {
142
                processingEnv.getMessager().printMessage(Kind.ERROR, "Comments must precede keys", e);
143
            }
144
        }
145
        for (Map.Entry<String,Map<String,String>> entry : pairs.entrySet()) {
146
            String pkg = entry.getKey();
147
            Map<String,String> keysAndValues = entry.getValue();
148
            Element[] elements = originatingElements.get(pkg).toArray(new Element[0]);
149
            try {
150
                EditableProperties p = new EditableProperties(true);
151
                // Load any preexisting bundle so we can just add our keys.
152
                try {
153
                    InputStream is = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, pkg, "Bundle.properties").openInputStream();
154
                    try {
155
                        p.load(is);
156
                    } finally {
157
                        is.close();
158
                    }
159
                } catch (IOException x) {
160
                    // OK, not there
161
                }
162
                for (String key : p.keySet()) {
163
                    if (keysAndValues.containsKey(key)) {
164
                        processingEnv.getMessager().printMessage(Kind.ERROR, "Key " + key + " is a duplicate of one from Bundle.properties", elements[0]);
165
                    }
166
                }
167
                // Also check class output for (1) incremental builds, (2) preexisting bundles from Maven projects.
168
                try {
169
                    InputStream is = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, "Bundle.properties").openInputStream();
170
                    try {
171
                        // do not use p.load(is) as the impl in EditableProperties does not currently handle duplicates properly
172
                        EditableProperties p2 = new EditableProperties(true);
173
                        p2.load(is);
174
                        p.putAll(p2);
175
                    } finally {
176
                        is.close();
177
                    }
178
                } catch (IOException x) {
179
                    // OK, not there
180
                }
181
                p.putAll(keysAndValues);
182
                for (Map.Entry<String,String[]> entry2 : comments.get(pkg).entrySet()) {
183
                    p.setComment(entry2.getKey(), entry2.getValue(), false);
184
                }
185
                OutputStream os = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, pkg, "Bundle.properties", elements).openOutputStream();
186
                try {
187
                    p.store(os);
188
                } finally {
189
                    os.close();
190
                }
191
                Map</*identifier*/String,/*method body*/String> methods = new TreeMap<String,String>();
192
                try {
193
                    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));
194
                    while (m.find()) {
195
                        methods.put(m.group(1), m.group());
196
                    }
197
                } catch (IOException x) {
198
                    // OK, not there
199
                }
200
                for (Map.Entry<String, String> entry2 : keysAndValues.entrySet()) {
201
                    String key = entry2.getKey();
202
                    String value = entry2.getValue();
203
                    StringBuilder method = new StringBuilder();
204
                    method.append("    /**\n");
205
                    List<String> params = new ArrayList<String>();
206
                    int i = 0;
207
                    while (value.contains("{" + i)) {
208
                        params.add("arg" + i++);
209
                    }
210
                    String[] commentLines = comments.get(pkg).get(key);
211
                    if (commentLines != null) {
212
                        for (String comment : commentLines) {
213
                            Matcher m = Pattern.compile("# [{](\\d+)[}] - (.+)").matcher(comment);
214
                            if (m.matches()) {
215
                                i = Integer.parseInt(m.group(1));
216
                                String desc = m.group(2);
217
                                params.set(i, toIdentifier(desc));
218
                                method.append("     * @param ").append(params.get(i)).append(" ").append(toJavadoc(desc)).append("\n");
219
                            }
220
                        }
221
                    }
222
                    method.append("     * @return ").append(toJavadoc(value)).append("\n");
223
                    method.append("     */\n");
224
                    String name = toIdentifier(key);
225
                    method.append("    static String ").append(name).append("(");
226
                    boolean first = true;
227
                    for (String param : params) {
228
                        if (first) {
229
                            first = false;
230
                        } else {
231
                            method.append(", ");
232
                        }
233
                        method.append("Object ").append(param);
234
                    }
235
                    method.append(") {\n");
236
                    method.append("        return org.openide.util.NbBundle.getMessage(Bundle.class, \"").append(key).append("\"");
237
                    for (String param : params) {
238
                        method.append(", ").append(param);
239
                    }
240
                    method.append(");\n");
241
                    method.append("    }\n");
242
                    methods.put(name, method.toString());
243
                }
244
                String fqn = pkg + ".Bundle";
245
                Writer w = processingEnv.getFiler().createSourceFile(fqn, elements).openWriter();
246
                try {
247
                    PrintWriter pw = new PrintWriter(w);
248
                    pw.println("package " + pkg + ";");
249
                    pw.println("/** Localizable strings for {@link " + pkg + "}. */");
250
                    pw.println("class Bundle {");
251
                    for (String method : methods.values()) {
252
                        pw.print(method);
253
                    }
254
                    pw.println("    private void Bundle() {}");
255
                    pw.println("}");
256
                    pw.flush();
257
                    pw.close();
258
                } finally {
259
                    w.close();
260
                }
261
            } catch (IOException x) {
262
                processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate files: " + x, elements[0]);
263
            }
264
        }
265
        return true;
266
    }
267
268
    private String findPackage(Element e) {
269
        switch (e.getKind()) {
270
        case PACKAGE:
271
            return ((PackageElement) e).getQualifiedName().toString();
272
        default:
273
            return findPackage(e.getEnclosingElement());
274
        }
275
    }
276
277
    private String toIdentifier(String key) {
278
        if (Utilities.isJavaIdentifier(key)) {
279
            return key;
280
        } else {
281
            String i = key.replaceAll("[^\\p{javaJavaIdentifierPart}]+", "_");
282
            if (Utilities.isJavaIdentifier(i)) {
283
                return i;
284
            } else {
285
                return "_" + i;
286
            }
287
        }
288
    }
289
290
    private String toJavadoc(String text) {
291
        return text.replace("&", "&amp;").replace("<", "&lt;").replace("*/", "&#x2A;/").replace("\n", "<br>").replace("@", "&#64;");
292
    }
293
294
}
(-)a/openide.util/src/org/openide/util/NbBundle.java (+67 lines)
Lines 46-51 Link Here
46
46
47
import java.io.IOException;
47
import java.io.IOException;
48
import java.io.InputStream;
48
import java.io.InputStream;
49
import java.lang.annotation.ElementType;
50
import java.lang.annotation.Retention;
51
import java.lang.annotation.RetentionPolicy;
52
import java.lang.annotation.Target;
49
import java.lang.ref.Reference;
53
import java.lang.ref.Reference;
50
import java.lang.ref.WeakReference;
54
import java.lang.ref.WeakReference;
51
import java.net.URL;
55
import java.net.URL;
Lines 81-86 Link Here
81
* </pre>
85
* </pre>
82
* will in German locale look for the key {@code Foo.displayName} in
86
* will in German locale look for the key {@code Foo.displayName} in
83
* {@code com/mycom/Bundle_de.properties} and then {@code com/mycom/Bundle.properties} (in that order).
87
* {@code com/mycom/Bundle_de.properties} and then {@code com/mycom/Bundle.properties} (in that order).
88
 * Usually however it is easiest to use {@link org.openide.util.NbBundle.Messages}.
84
*/
89
*/
85
public class NbBundle extends Object {
90
public class NbBundle extends Object {
86
91
Lines 771-776 Link Here
771
    }
776
    }
772
777
773
    /**
778
    /**
779
     * Creates a helper class with static definitions of bundle keys.
780
     * <p>
781
     * The generated class will be called {@code Bundle} and be in the same package.
782
     * Each key is placed in a {@code Bundle.properties} file also in the same package,
783
     * and the helper class gets a method with the same name as the key
784
     * (converted to a valid Java identifier as needed)
785
     * which loads the key from the (possibly now localized) bundle using {@link NbBundle#getMessage(Class, String)}.
786
     * The method will have as many arguments (of type {@code Object}) as there are message format parameters.
787
     * </p>
788
     * <p>It is an error to duplicate a key within a package, even if the duplicates are from different compilation units.</p>
789
     * <p>Example usage:</p>
790
     * <pre>
791
     * package some.where;
792
     * import org.openide.util.NbBundle.Messages;
793
     * import static some.where.Bundle.*;
794
     * import org.openide.DialogDisplayer;
795
     * import org.openide.NotifyDescriptor;
796
     * class Something {
797
     *     &#64;Messages({
798
     *         "dialog.title=Bad File",
799
     *         "# {0} - file path",
800
     *         "dialog.message=The file {0} was invalid."
801
     *     })
802
     *     void showError(File f) {
803
     *         NotifyDescriptor d = new NotifyDescriptor.Message(
804
     *             dialog_message(f), NotifyDescriptor.ERROR_MESSAGE);
805
     *         d.setTitle(dialog_title());
806
     *         DialogDisplayer.getDefault().notify(d);
807
     *     }
808
     * }
809
     * </pre>
810
     * <p>which generates during compilation {@code Bundle.java}:</p>
811
     * <pre>
812
     * class Bundle {
813
     *     static String dialog_title() {...}
814
     *     static String dialog_message(Object file_path) {...}
815
     * }
816
     * </pre>
817
     * <p>and {@code Bundle.properties}:</p>
818
     * <pre>
819
     * title=Bad File
820
     * # {0} - file path
821
     * message=The file {0} was invalid.
822
     * </pre>
823
     * @since org.openide.util 8.10
824
     */
825
    @Retention(RetentionPolicy.SOURCE)
826
    @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
827
    public @interface Messages {
828
        /**
829
         * List of key/value pairs.
830
         * Each must be of the form {@code key=Some Value}.
831
         * Anything is permitted in the value, including newlines.
832
         * Unlike in a properties file, there should be no whitespace before the key or around the equals sign.
833
         * Values containing <code>{0}</code> etc. are assumed to be message formats and so may need escapes for metacharacters such as {@code '}.
834
         * A line may also be a comment if it starts with {@code #}, which may be useful for translators;
835
         * it is recommended to use the format {@code # {0} - summary of param}.
836
         */
837
        String[] value();
838
    }
839
840
    /**
774
     * Do not use.
841
     * Do not use.
775
     * @deprecated Useless.
842
     * @deprecated Useless.
776
     */
843
     */
(-)a/openide.util/test/unit/src/org/netbeans/modules/openide/util/NbBundleProcessorTest.java (+205 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2010 Sun Microsystems, Inc.
41
 */
42
43
package org.netbeans.modules.openide.util;
44
45
import java.net.URL;
46
import java.io.File;
47
import java.io.ByteArrayOutputStream;
48
import java.net.URLClassLoader;
49
import org.openide.util.test.AnnotationProcessorTestUtils;
50
import org.netbeans.junit.NbTestCase;
51
import org.openide.util.NbBundle.Messages;
52
import org.openide.util.test.TestFileUtils;
53
import static org.netbeans.modules.openide.util.Bundle.*;
54
55
@Messages("k3=value #3")
56
public class NbBundleProcessorTest extends NbTestCase {
57
58
    public NbBundleProcessorTest(String n) {
59
        super(n);
60
    }
61
62
    private File src;
63
    private File dest;
64
    protected @Override void setUp() throws Exception {
65
        clearWorkDir();
66
        src = new File(getWorkDir(), "src");
67
        dest = new File(getWorkDir(), "classes");
68
    }
69
70
    @Messages({
71
        "k1=value #1",
72
        "k2=value #2"
73
    })
74
    public void testBasicUsage() throws Exception {
75
        assertEquals("value #1", k1());
76
        assertEquals("value #2", k2());
77
        assertEquals("value #3", k3());
78
    }
79
80
    @Messages({
81
        "f1=problem with {0}",
82
        "# {0} - input file",
83
        "# {1} - pattern",
84
        "f2={0} did not match {1}",
85
        "LBL_BuildMainProjectAction_Name=&Build {0,choice,-1#Main Project|0#Project|1#Project ({1})|1<{0} Projects}"
86
    })
87
    public void testMessageFormats() throws Exception {
88
        assertEquals("problem with stuff", f1("stuff"));
89
        assertEquals("1 did not match 2", f2(1, 2));
90
        assertEquals("&Build Main Project", LBL_BuildMainProjectAction_Name(-1, "whatever"));
91
        assertEquals("&Build Project", LBL_BuildMainProjectAction_Name(0, "whatever"));
92
        assertEquals("&Build Project (whatever)", LBL_BuildMainProjectAction_Name(1, "whatever"));
93
        assertEquals("&Build 2 Projects", LBL_BuildMainProjectAction_Name(2, "whatever"));
94
    }
95
96
    @Messages({
97
        "s1=Don't worry",
98
        "s2=Don''t worry about {0}",
99
        "s3=@camera Say \"cheese\"",
100
        "s4=<bra&ket>",
101
        "s5=Operators: +-*/=",
102
        "s6=One thing.\nAnd another."
103
    })
104
    public void testSpecialCharacters() throws Exception {
105
        assertEquals("Don't worry", s1());
106
        assertEquals("Don't worry about me", s2("me"));
107
        assertEquals("@camera Say \"cheese\"", s3());
108
        assertEquals("<bra&ket>", s4());
109
        assertEquals("Operators: +-*/=", s5());
110
        assertEquals("One thing.\nAnd another.", s6());
111
    }
112
113
    @Messages({
114
        "some key=some value",
115
        "public=property",
116
        "2+2=4"
117
    })
118
    public void testNonIdentifierKeys() throws Exception {
119
        assertEquals("some value", some_key());
120
        assertEquals("property", _public());
121
        assertEquals("4", _2_2());
122
    }
123
124
    public void testPackageKeys() throws Exception {
125
        assertEquals("stuff", org.netbeans.modules.openide.util.Bundle.general());
126
    }
127
128
    public void testDupeErrorSimple() throws Exception {
129
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages({\"k=v1\", \"k=v2\"})", "class C {}");
130
        ByteArrayOutputStream err = new ByteArrayOutputStream();
131
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err));
132
        assertTrue(err.toString(), err.toString().contains("uplicate"));
133
    }
134
135
    public void testDupeErrorByIdentifier() throws Exception {
136
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages({\"k.=v1\", \"k,=v2\"})", "class C {}");
137
        ByteArrayOutputStream err = new ByteArrayOutputStream();
138
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err));
139
        assertTrue(err.toString(), err.toString().contains("uplicate"));
140
    }
141
142
    public void testDupeErrorAcrossClasses() throws Exception {
143
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C1", "@org.openide.util.NbBundle.Messages({\"k=v\"})", "class C1 {}");
144
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C2", "@org.openide.util.NbBundle.Messages({\"k=v\"})", "class C2 {}");
145
        ByteArrayOutputStream err = new ByteArrayOutputStream();
146
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err));
147
        assertTrue(err.toString(), err.toString().contains("uplicate"));
148
    }
149
150
    public void testNoEqualsError() throws Exception {
151
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages(\"whatever\")", "class C {}");
152
        ByteArrayOutputStream err = new ByteArrayOutputStream();
153
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err));
154
        assertTrue(err.toString(), err.toString().contains("="));
155
    }
156
157
    public void testWhitespaceError() throws Exception {
158
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages(\"key = value\")", "class C {}");
159
        ByteArrayOutputStream err = new ByteArrayOutputStream();
160
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), null, getWorkDir(), null, err));
161
        assertTrue(err.toString(), err.toString().contains("="));
162
    }
163
164
    public void testExistingBundle() throws Exception {
165
        AnnotationProcessorTestUtils.makeSource(src, "p.C", "@org.openide.util.NbBundle.Messages(\"k=v\")", "class C {}");
166
        TestFileUtils.writeFile(new File(src, "p/Bundle.properties"), "# original comment\nold=stuff\n");
167
        assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null));
168
        assertEquals("k=v\n# original comment\nold=stuff\n", TestFileUtils.readFile(new File(dest, "p/Bundle.properties")));
169
        // Also check that we can recompile:
170
        assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null));
171
        assertEquals("k=v\n# original comment\nold=stuff\n", TestFileUtils.readFile(new File(dest, "p/Bundle.properties")));
172
    }
173
174
    public void testDupeErrorWithExistingBundle() throws Exception {
175
        AnnotationProcessorTestUtils.makeSource(src, "p.C", "@org.openide.util.NbBundle.Messages(\"k=v\")", "class C {}");
176
        TestFileUtils.writeFile(new File(src, "p/Bundle.properties"), "k=v\n");
177
        ByteArrayOutputStream err = new ByteArrayOutputStream();
178
        assertFalse(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, err));
179
        assertTrue(err.toString(), err.toString().contains("uplicate"));
180
    }
181
182
    public void testIncrementalCompilation() throws Exception {
183
        AnnotationProcessorTestUtils.makeSource(src, "p.C1", "@org.openide.util.NbBundle.Messages(\"k1=v1\")", "public class C1 {public @Override String toString() {return Bundle.k1();}}");
184
        AnnotationProcessorTestUtils.makeSource(src, "p.C2", "@org.openide.util.NbBundle.Messages(\"k2=v2\")", "public class C2 {public @Override String toString() {return Bundle.k2();}}");
185
        assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null));
186
        ClassLoader l = new URLClassLoader(new URL[] {dest.toURI().toURL()});
187
        assertEquals("v1", l.loadClass("p.C1").newInstance().toString());
188
        assertEquals("v2", l.loadClass("p.C2").newInstance().toString());
189
        AnnotationProcessorTestUtils.makeSource(src, "p.C1", "@org.openide.util.NbBundle.Messages(\"k1=v3\")", "public class C1 {public @Override String toString() {return Bundle.k1();}}");
190
        assertTrue(AnnotationProcessorTestUtils.runJavac(src, "C1.java", dest, null, null));
191
        l = new URLClassLoader(new URL[] {dest.toURI().toURL()});
192
        assertEquals("v3", l.loadClass("p.C1").newInstance().toString());
193
        assertEquals("v2", l.loadClass("p.C2").newInstance().toString());
194
    }
195
196
    public void testComments() throws Exception {
197
        AnnotationProcessorTestUtils.makeSource(src, "p.C", "@org.openide.util.NbBundle.Messages({\"# Something skvělý to note.\", \"k=v\"})", "class C {}");
198
        assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null));
199
        assertEquals("# Something skv\\u011bl\\u00fd to note.\nk=v\n", TestFileUtils.readFile(new File(dest, "p/Bundle.properties")));
200
        // Also check that we can recompile:
201
        assertTrue(AnnotationProcessorTestUtils.runJavac(src, null, dest, null, null));
202
        assertEquals("# Something skv\\u011bl\\u00fd to note.\nk=v\n", TestFileUtils.readFile(new File(dest, "p/Bundle.properties")));
203
    }
204
205
}
(-)a/openide.util/test/unit/src/org/netbeans/modules/openide/util/package-info.java (+45 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2010 Sun Microsystems, Inc.
41
 */
42
43
@Messages("general=stuff")
44
package org.netbeans.modules.openide.util;
45
import org.openide.util.NbBundle.Messages;

Return to bug 192750