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.util/src/org/netbeans/modules/openide/util/NbBundleProcessor.java (+209 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.OutputStream;
47
import java.io.PrintWriter;
48
import java.io.Writer;
49
import java.util.ArrayList;
50
import java.util.Collections;
51
import java.util.HashMap;
52
import java.util.HashSet;
53
import java.util.List;
54
import java.util.Map;
55
import java.util.Set;
56
import javax.annotation.processing.AbstractProcessor;
57
import javax.annotation.processing.Processor;
58
import javax.annotation.processing.RoundEnvironment;
59
import javax.annotation.processing.SupportedSourceVersion;
60
import javax.lang.model.SourceVersion;
61
import javax.lang.model.element.Element;
62
import javax.lang.model.element.PackageElement;
63
import javax.lang.model.element.TypeElement;
64
import javax.tools.Diagnostic.Kind;
65
import javax.tools.StandardLocation;
66
import org.openide.util.EditableProperties;
67
import org.openide.util.NbBundle;
68
import org.openide.util.Utilities;
69
import org.openide.util.lookup.ServiceProvider;
70
71
@ServiceProvider(service = Processor.class)
72
@SupportedSourceVersion(SourceVersion.RELEASE_6)
73
public class NbBundleProcessor extends AbstractProcessor {
74
75
    public @Override Set<String> getSupportedAnnotationTypes() {
76
        return Collections.singleton(NbBundle.Messages.class.getCanonicalName());
77
    }
78
79
    public @Override boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
80
        if (roundEnv.processingOver()) {
81
            return false;
82
        }
83
        Map</*package*/String,Map</*key*/String,/*value*/String>> pairs = new HashMap<String,Map<String,String>>();
84
        Map</*package*/String,Set</*identifier*/String>> identifiers = new HashMap<String,Set<String>>();
85
        Map</*package*/String,List<Element>> originatingElements = new HashMap<String,List<Element>>();
86
        for (Element e : roundEnv.getElementsAnnotatedWith(NbBundle.Messages.class)) {
87
            String pkg = findPackage(e);
88
            try {
89
                processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, pkg, "Bundle.properties").openInputStream().close();
90
                processingEnv.getMessager().printMessage(Kind.ERROR, "Bundle.properties already exists in " + pkg, e);
91
                continue;
92
            } catch (IOException x) {
93
                // fine, does not exist
94
            }
95
            for (String keyValue : e.getAnnotation(NbBundle.Messages.class).value()) {
96
                int i = keyValue.indexOf('=');
97
                if (i == -1) {
98
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Bad key=value: " + keyValue, e);
99
                    continue;
100
                }
101
                String key = keyValue.substring(0, i);
102
                if (key.isEmpty() || !key.equals(key.trim())) {
103
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Whitespace not permitted in key: " + keyValue, e);
104
                    continue;
105
                }
106
                Set<String> identifiersByPackage = identifiers.get(pkg);
107
                if (identifiersByPackage == null) {
108
                    identifiersByPackage = new HashSet<String>();
109
                    identifiers.put(pkg, identifiersByPackage);
110
                }
111
                if (!identifiersByPackage.add(toIdentifier(key))) {
112
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate key: " + key, e);
113
                    continue;
114
                }
115
                String value = keyValue.substring(i + 1);
116
                Map<String,String> pairsByPackage = pairs.get(pkg);
117
                if (pairsByPackage == null) {
118
                    pairsByPackage = new HashMap<String,String>();
119
                    pairs.put(pkg, pairsByPackage);
120
                }
121
                pairsByPackage.put(key, value);
122
                List<Element> originatingElementsByPackage = originatingElements.get(pkg);
123
                if (originatingElementsByPackage == null) {
124
                    originatingElementsByPackage = new ArrayList<Element>();
125
                    originatingElements.put(pkg, originatingElementsByPackage);
126
                }
127
                originatingElementsByPackage.add(e);
128
            }
129
        }
130
        for (Map.Entry<String,Map<String,String>> entry : pairs.entrySet()) {
131
            String pkg = entry.getKey();
132
            Map<String,String> keysAndValues = entry.getValue();
133
            Element[] elements = originatingElements.get(pkg).toArray(new Element[0]);
134
            try {
135
                OutputStream os = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, pkg, "Bundle.properties", elements).openOutputStream();
136
                try {
137
                    EditableProperties p = new EditableProperties(true);
138
                    p.putAll(keysAndValues);
139
                    p.store(os);
140
                } finally {
141
                    os.close();
142
                }
143
                String fqn = pkg + ".Bundle";
144
                Writer w = processingEnv.getFiler().createSourceFile(fqn, elements).openWriter();
145
                try {
146
                    PrintWriter pw = new PrintWriter(w);
147
                    pw.println("package " + pkg + ";");
148
                    pw.println("/** Localizable strings for {@link " + pkg + "}. */");
149
                    pw.println("class Bundle {");
150
                    for (Map.Entry<String,String> entry2 : keysAndValues.entrySet()) {
151
                        String key = entry2.getKey();
152
                        String value = entry2.getValue();
153
                        pw.println("    /** " + value.replace("&", "&amp;").replace("<", "&lt;").replace("*/", "&#x2A;/").replace("\n", "<br>").replace("@", "&#64;") + " */");
154
                        pw.print("    static String " + toIdentifier(key) + "(");
155
                        int params = 0;
156
                        while (value.contains("{" + params)) {
157
                            params++;
158
                        }
159
                        for (int i = 0; i < params; i++) {
160
                            if (i > 0) {
161
                                pw.print(", ");
162
                            }
163
                            pw.print("Object arg" + i);
164
                        }
165
                        pw.println(") {");
166
                        pw.print("        return org.openide.util.NbBundle.getMessage(Bundle.class, \"" + key + "\"");
167
                        for (int i = 0; i < params; i++) {
168
                            pw.print(", arg" + i);
169
                        }
170
                        pw.println(");");
171
                        pw.println("    }");
172
                    }
173
                    pw.println("    private void Bundle() {}");
174
                    pw.println("}");
175
                    pw.flush();
176
                    pw.close();
177
                } finally {
178
                    w.close();
179
                }
180
            } catch (IOException x) {
181
                processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate files: " + x, elements[0]);
182
            }
183
        }
184
        return true;
185
    }
186
187
    private String findPackage(Element e) {
188
        switch (e.getKind()) {
189
        case PACKAGE:
190
            return ((PackageElement) e).getQualifiedName().toString();
191
        default:
192
            return findPackage(e.getEnclosingElement());
193
        }
194
    }
195
196
    private String toIdentifier(String key) {
197
        if (Utilities.isJavaIdentifier(key)) {
198
            return key;
199
        } else {
200
            String i = key.replaceAll("[^\\p{javaJavaIdentifierPart}]", "_");
201
            if (Utilities.isJavaIdentifier(i)) {
202
                return i;
203
            } else {
204
                return "_" + i;
205
            }
206
        }
207
    }
208
209
}
(-)a/openide.util/src/org/openide/util/NbBundle.java (+63 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 771-776 Link Here
771
    }
775
    }
772
776
773
    /**
777
    /**
778
     * Creates a helper class with static definitions of bundle keys.
779
     * <p>
780
     * The generated class will be called {@code Bundle} and be in the same package.
781
     * Each key is placed in a {@code Bundle.properties} file also in the same package,
782
     * and the helper class gets a method with the same name as the key
783
     * (converted to a valid Java identifier as needed)
784
     * which loads the key from the (possibly now localized) bundle using {@link NbBundle#getMessage(Class, String)}.
785
     * The method will have as many arguments (of type {@code Object}) as there are message format parameters.
786
     * </p>
787
     * <p>It is an error to duplicate a key within a package, even if the duplicates are from different compilation units.</p>
788
     * <p>Since a file {@code Bundle.properties} is generated, if converting code to use this annotation, you must do a whole package at a time.</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
     *         "dialog.message=The file {0} was invalid."
800
     *     })
801
     *     void showError(File f) {
802
     *         NotifyDescriptor d = new NotifyDescriptor.Message(
803
     *             dialog_message(f), NotifyDescriptor.ERROR_MESSAGE);
804
     *         d.setTitle(dialog_title());
805
     *         DialogDisplayer.getDefault().notify(d);
806
     *     }
807
     * }
808
     * </pre>
809
     * <p>which generates during compilation {@code Bundle.java}:</p>
810
     * <pre>
811
     * class Bundle {
812
     *     static String dialog_title() {...}
813
     *     static String dialog_message(Object arg0) {...}
814
     * }
815
     * </pre>
816
     * <p>and {@code Bundle.properties}:</p>
817
     * <pre>
818
     * title=Bad File
819
     * message=The file {0} was invalid.
820
     * </pre>
821
     * @since XXX
822
     */
823
    @Retention(RetentionPolicy.SOURCE)
824
    @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
825
    public @interface Messages {
826
        /**
827
         * List of key/value pairs.
828
         * Each must be of the form {@code key=Some Value} where {@code key} is a valid Java identifier.
829
         * Anything is permitted in the value, including newlines.
830
         * Unlike in a properties file, there should be no whitespace before the key or around the equals sign.
831
         * Values containing <code>{0}</code> etc. are assumed to be message formats and so may need escapes for metacharacters such as {@code '}.
832
         */
833
        String[] value();
834
    }
835
836
    /**
774
     * Do not use.
837
     * Do not use.
775
     * @deprecated Useless.
838
     * @deprecated Useless.
776
     */
839
     */
(-)a/openide.util/test/unit/src/org/netbeans/modules/openide/util/NbBundleProcessorTest.java (+160 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.File;
46
import java.io.ByteArrayOutputStream;
47
import org.openide.util.test.AnnotationProcessorTestUtils;
48
import org.netbeans.junit.NbTestCase;
49
import org.openide.util.NbBundle.Messages;
50
import org.openide.util.test.TestFileUtils;
51
import static org.netbeans.modules.openide.util.Bundle.*;
52
53
@Messages("k3=value #3")
54
public class NbBundleProcessorTest extends NbTestCase {
55
56
    public NbBundleProcessorTest(String n) {
57
        super(n);
58
    }
59
60
    protected @Override void setUp() throws Exception {
61
        clearWorkDir();
62
    }
63
64
    @Messages({
65
        "k1=value #1",
66
        "k2=value #2"
67
    })
68
    public void testBasicUsage() throws Exception {
69
        assertEquals("value #1", k1());
70
        assertEquals("value #2", k2());
71
        assertEquals("value #3", k3());
72
    }
73
74
    @Messages({
75
        "f1=problem with {0}",
76
        "f2={0} did not match {1}",
77
        "LBL_BuildMainProjectAction_Name=&Build {0,choice,-1#Main Project|0#Project|1#Project ({1})|1<{0} Projects}"
78
    })
79
    public void testMessageFormats() throws Exception {
80
        assertEquals("problem with stuff", f1("stuff"));
81
        assertEquals("1 did not match 2", f2(1, 2));
82
        assertEquals("&Build Main Project", LBL_BuildMainProjectAction_Name(-1, "whatever"));
83
        assertEquals("&Build Project", LBL_BuildMainProjectAction_Name(0, "whatever"));
84
        assertEquals("&Build Project (whatever)", LBL_BuildMainProjectAction_Name(1, "whatever"));
85
        assertEquals("&Build 2 Projects", LBL_BuildMainProjectAction_Name(2, "whatever"));
86
    }
87
88
    @Messages({
89
        "s1=Don't worry",
90
        "s2=Don''t worry about {0}",
91
        "s3=@camera Say \"cheese\"",
92
        "s4=<bra&ket>",
93
        "s5=Operators: +-*/=",
94
        "s6=One thing.\nAnd another."
95
    })
96
    public void testSpecialCharacters() throws Exception {
97
        assertEquals("Don't worry", s1());
98
        assertEquals("Don't worry about me", s2("me"));
99
        assertEquals("@camera Say \"cheese\"", s3());
100
        assertEquals("<bra&ket>", s4());
101
        assertEquals("Operators: +-*/=", s5());
102
        assertEquals("One thing.\nAnd another.", s6());
103
    }
104
105
    @Messages({
106
        "some key=some value",
107
        "public=property",
108
        "2+2=4"
109
    })
110
    public void testNonIdentifierKeys() throws Exception {
111
        assertEquals("some value", some_key());
112
        assertEquals("property", _public());
113
        assertEquals("4", _2_2());
114
    }
115
116
    public void testPackageKeys() throws Exception {
117
        assertEquals("stuff", org.netbeans.modules.openide.util.Bundle.general());
118
    }
119
120
    public void testDupeErrorSimple() throws Exception {
121
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages({\"k=v1\", \"k=v2\"})", "class C {}");
122
        ByteArrayOutputStream err = new ByteArrayOutputStream();
123
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), "C.java", getWorkDir(), null, err));
124
        assertTrue(err.toString(), err.toString().contains("uplicate"));
125
    }
126
127
    public void testDupeErrorByIdentifier() throws Exception {
128
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages({\"k.=v1\", \"k,=v2\"})", "class C {}");
129
        ByteArrayOutputStream err = new ByteArrayOutputStream();
130
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), "C.java", getWorkDir(), null, err));
131
        assertTrue(err.toString(), err.toString().contains("uplicate"));
132
    }
133
134
    // XXX key conflicts among multiple classes in same package
135
136
    public void testNoEqualsError() throws Exception {
137
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages(\"whatever\")", "class C {}");
138
        ByteArrayOutputStream err = new ByteArrayOutputStream();
139
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), "C.java", getWorkDir(), null, err));
140
        assertTrue(err.toString(), err.toString().contains("="));
141
    }
142
143
    public void testWhitespaceError() throws Exception {
144
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages(\"key = value\")", "class C {}");
145
        ByteArrayOutputStream err = new ByteArrayOutputStream();
146
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), "C.java", getWorkDir(), null, err));
147
        assertTrue(err.toString(), err.toString().contains("="));
148
    }
149
150
    public void testExistingBundle() throws Exception {
151
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C", "@org.openide.util.NbBundle.Messages(\"k=v\")", "class C {}");
152
        TestFileUtils.writeFile(new File(getWorkDir(), "p/Bundle.properties"), "old=stuff");
153
        ByteArrayOutputStream err = new ByteArrayOutputStream();
154
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), "C.java", getWorkDir(), null, err));
155
        assertTrue(err.toString(), err.toString().contains("Bundle.properties"));
156
    }
157
158
    // XXX test incremental compilation of multiple classes in the same package
159
160
}
(-)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