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/NbBundleKeysProcessor.java (+219 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.List;
53
import java.util.Map;
54
import java.util.Set;
55
import java.util.TreeMap;
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 NbBundleKeysProcessor extends AbstractProcessor {
74
75
    public @Override Set<String> getSupportedAnnotationTypes() {
76
        return Collections.singleton(NbBundle.Keys.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</*basename*/String,Map</*key*/String,/*value*/String>>> pairs = new HashMap<String,Map<String,Map<String,String>>>();
84
        Map</*package*/String,Map</*basename*/String,List<Element>>> originatingElements = new HashMap<String,Map<String,List<Element>>>();
85
        for (Element e : roundEnv.getElementsAnnotatedWith(NbBundle.Keys.class)) {
86
            for (String keyValue : e.getAnnotation(NbBundle.Keys.class).value()) {
87
                int i = keyValue.indexOf('=');
88
                if (i == -1) {
89
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Bad key=value: " + keyValue, e);
90
                    continue;
91
                }
92
                String key = keyValue.substring(0, i);
93
                if (!Utilities.isJavaIdentifier(key)) {
94
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Not a Java identifier: " + key, e);
95
                    continue;
96
                }
97
                String value = keyValue.substring(i + 1);
98
                String pkg = findPackage(e);
99
                String basename = findBasename(e);
100
                Map<String,Map<String,String>> pairsByPackage = pairs.get(pkg);
101
                if (pairsByPackage == null) {
102
                    pairsByPackage = new HashMap<String,Map<String,String>>();
103
                    pairs.put(pkg, pairsByPackage);
104
                }
105
                Map<String,String> pairsByBasename = pairsByPackage.get(basename);
106
                if (pairsByBasename == null) {
107
                    pairsByBasename = new TreeMap<String,String>();
108
                    pairsByPackage.put(basename, pairsByBasename);
109
                }
110
                if (pairsByBasename.containsKey(key)) {
111
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate key: " + key, e);
112
                    continue;
113
                }
114
                pairsByBasename.put(key, value);
115
                Map<String,List<Element>> originatingElementsByPackage = originatingElements.get(pkg);
116
                if (originatingElementsByPackage == null) {
117
                    originatingElementsByPackage = new HashMap<String,List<Element>>();
118
                    originatingElements.put(pkg, originatingElementsByPackage);
119
                }
120
                List<Element> originatingElementsByBasename = originatingElementsByPackage.get(basename);
121
                if (originatingElementsByBasename == null) {
122
                    originatingElementsByBasename = new ArrayList<Element>();
123
                    originatingElementsByPackage.put(basename, originatingElementsByBasename);
124
                }
125
                originatingElementsByBasename.add(e);
126
            }
127
        }
128
        for (Map.Entry<String,Map<String,Map<String,String>>> entry : pairs.entrySet()) {
129
            String pkg = entry.getKey();
130
            for (Map.Entry<String,Map<String,String>> entry2 : entry.getValue().entrySet()) {
131
                String basename = entry2.getKey();
132
                Map<String,String> keysAndValues = entry2.getValue();
133
                Element[] elements = originatingElements.get(pkg).get(basename).toArray(new Element[0]);
134
                try {
135
                    OutputStream os = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, pkg, basename + ".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 + "." + basename;
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 + (basename.equals("Bundle") ? "" : "." + basename.substring(0, basename.length() - 6)) + "}. */");
149
                        pw.println("class " + basename + " {");
150
                        for (Map.Entry<String,String> entry3 : keysAndValues.entrySet()) {
151
                            String key = entry3.getKey();
152
                            String value = entry3.getValue();
153
                            pw.println("    /** " + value.replace("&", "&amp;").replace("<", "&lt;").replace("*/", "&#x2A;/").replace("\n", "<br>").replace("@", "&#64;") + " */");
154
                            pw.print("    static String " + 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
                            if (params > 0) {
167
                                pw.print("        return java.text.MessageFormat.format($bundle().getString(\"" + key + "\")");
168
                                for (int i = 0; i < params; i++) {
169
                                    pw.print(", arg" + i);
170
                                }
171
                                pw.println(");");
172
                            } else {
173
                                pw.println("        return $bundle().getString(\"" + key + "\");");
174
                            }
175
                            pw.println("    }");
176
                        }
177
                        pw.println("    private static java.util.ResourceBundle $bundle() {");
178
                        pw.println("        return org.openide.util.NbBundle.getBundle(\"" + fqn + "\", java.util.Locale.getDefault(), " + basename + ".class.getClassLoader());");
179
                        pw.println("    }");
180
                        pw.println("    private void " + basename + "() {}");
181
                        pw.println("}");
182
                        pw.flush();
183
                        pw.close();
184
                    } finally {
185
                        w.close();
186
                    }
187
                } catch (IOException x) {
188
                    processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate files: " + x, elements[0]);
189
                }
190
            }
191
        }
192
        return true;
193
    }
194
195
    private String findPackage(Element e) {
196
        switch (e.getKind()) {
197
        case PACKAGE:
198
            return ((PackageElement) e).getQualifiedName().toString();
199
        default:
200
            return findPackage(e.getEnclosingElement());
201
        }
202
    }
203
204
    private String findBasename(Element e) {
205
        switch (e.getKind()) {
206
        case PACKAGE:
207
            return "Bundle";
208
        default:
209
            Element outer = e.getEnclosingElement();
210
            switch (outer.getKind()) {
211
            case PACKAGE:
212
                return e.getSimpleName() + "Bundle";
213
            default:
214
                return findBasename(outer);
215
            }
216
        }
217
    }
218
219
}
(-)a/openide.util/src/org/openide/util/NbBundle.java (+66 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
     * <ul>
780
     * <li>If placed on a top-level type {@code Something}, creates {@code SomethingBundle} in the same package.
781
     * <li>If placed on a method, constructor, or nested class, creates/appends to the bundle corresponding to the top-level type.
782
     *      (It is an error to duplicate a key within a helper class, even if the duplicates are from different nested elements.)
783
     * <li>If placed on a package (not recommended), creates a class {@code Bundle} in that package.
784
     * </ul>
785
     * <p>
786
     * Each key is placed in a {@code *.properties} file matching the helper class,
787
     * and the helper class gets a method with the same name as the key
788
     * which loads the key from the (possibly now localized) bundle using {@code NbBundle} with the default
789
     * locale and the same class loader as the helper class.
790
     * The method will have as many arguments (of type {@code Object}) as there are message format parameters.
791
     * </p>
792
     * <p>Example usage:</p>
793
     * <pre>
794
     * package some.where;
795
     * import org.openide.util.NbBundle.Keys;
796
     * import static some.where.SomethingBundle.*;
797
     * import org.openide.DialogDisplayer;
798
     * import org.openide.NotifyDescriptor;
799
     * class Something {
800
     *     &#64;Keys({
801
     *         "title=Bad File",
802
     *         "message=The file {0} was invalid."
803
     *     })
804
     *     void showError(File f) {
805
     *         NotifyDescriptor d = new NotifyDescriptor.Message(
806
     *             message(f), NotifyDescriptor.ERROR_MESSAGE);
807
     *         d.setTitle(title());
808
     *         DialogDisplayer.getDefault().notify(d);
809
     *     }
810
     * }
811
     * </pre>
812
     * <p>which generates during compilation {@code SomethingBundle.java}:</p>
813
     * <pre>
814
     * class SomethingBundle {
815
     *     static String title() {...}
816
     *     static String message(Object arg0) {...}
817
     * }
818
     * </pre>
819
     * <p>and {@code SomethingBundle.properties}:</p>
820
     * <pre>
821
     * title=Bad File
822
     * message=The file {0} was invalid.
823
     * </pre>
824
     * @since XXX
825
     */
826
    @Retention(RetentionPolicy.SOURCE)
827
    @Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
828
    public @interface Keys {
829
        /**
830
         * List of key/value pairs.
831
         * Each must be of the form {@code key=Some Value} where {@code key} is a valid Java identifier.
832
         * Anything is permitted in the value, including newlines.
833
         * Unlike in a properties file, there should be no whitespace before the key or around the equals sign.
834
         * Values containing <code>{0}</code> etc. are assumed to be message formats and so may need escapes for metacharacters such as {@code '}.
835
         */
836
        String[] value();
837
    }
838
839
    /**
774
     * Do not use.
840
     * Do not use.
775
     * @deprecated Useless.
841
     * @deprecated Useless.
776
     */
842
     */
(-)a/openide.util/test/unit/src/org/netbeans/modules/openide/util/NbBundleKeysProcessorTest.java (+119 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.ByteArrayOutputStream;
46
import org.openide.util.test.AnnotationProcessorTestUtils;
47
import org.netbeans.junit.NbTestCase;
48
import org.openide.util.NbBundle.Keys;
49
import static org.netbeans.modules.openide.util.NbBundleKeysProcessorTestBundle.*;
50
51
@Keys("k3=value #3")
52
public class NbBundleKeysProcessorTest extends NbTestCase {
53
54
    public NbBundleKeysProcessorTest(String n) {
55
        super(n);
56
    }
57
58
    @Keys({
59
        "k1=value #1",
60
        "k2=value #2"
61
    })
62
    public void testBasicUsage() throws Exception {
63
        assertEquals("value #1", k1());
64
        assertEquals("value #2", k2());
65
        assertEquals("value #3", k3());
66
    }
67
68
    @Keys({
69
        "f1=problem with {0}",
70
        "f2={0} did not match {1}",
71
        "LBL_BuildMainProjectAction_Name=&Build {0,choice,-1#Main Project|0#Project|1#Project ({1})|1<{0} Projects}"
72
    })
73
    public void testMessageFormats() throws Exception {
74
        assertEquals("problem with stuff", f1("stuff"));
75
        assertEquals("1 did not match 2", f2(1, 2));
76
        assertEquals("&Build Main Project", LBL_BuildMainProjectAction_Name(-1, "whatever"));
77
        assertEquals("&Build Project", LBL_BuildMainProjectAction_Name(0, "whatever"));
78
        assertEquals("&Build Project (whatever)", LBL_BuildMainProjectAction_Name(1, "whatever"));
79
        assertEquals("&Build 2 Projects", LBL_BuildMainProjectAction_Name(2, "whatever"));
80
    }
81
82
    @Keys({
83
        "s1=Don't worry",
84
        "s2=Don''t worry about {0}",
85
        "s3=@camera Say \"cheese\"",
86
        "s4=<bra&ket>",
87
        "s5=Operators: +-*/=",
88
        "s6=One thing.\nAnd another."
89
    })
90
    public void testSpecialCharacters() throws Exception {
91
        assertEquals("Don't worry", s1());
92
        assertEquals("Don't worry about me", s2("me"));
93
        assertEquals("@camera Say \"cheese\"", s3());
94
        assertEquals("<bra&ket>", s4());
95
        assertEquals("Operators: +-*/=", s5());
96
        assertEquals("One thing.\nAnd another.", s6());
97
    }
98
99
    public void testPackageKeys() throws Exception {
100
        assertEquals("stuff", org.netbeans.modules.openide.util.Bundle.general());
101
    }
102
103
    public void testErrors() throws Exception {
104
        clearWorkDir();
105
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C1", "@org.openide.util.NbBundle.Keys({\"k=v1\", \"k=v2\"})", "class C1 {}");
106
        ByteArrayOutputStream err = new ByteArrayOutputStream();
107
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), "C1.java", getWorkDir(), null, err));
108
        assertTrue(err.toString(), err.toString().contains("uplicate"));
109
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C2", "@org.openide.util.NbBundle.Keys(\"not a key=v\")", "class C2 {}");
110
        err = new ByteArrayOutputStream();
111
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), "C2.java", getWorkDir(), null, err));
112
        assertTrue(err.toString(), err.toString().contains("Java identifier"));
113
        AnnotationProcessorTestUtils.makeSource(getWorkDir(), "p.C3", "@org.openide.util.NbBundle.Keys(\"whatever\")", "class C3 {}");
114
        err = new ByteArrayOutputStream();
115
        assertFalse(AnnotationProcessorTestUtils.runJavac(getWorkDir(), "C3.java", getWorkDir(), null, err));
116
        assertTrue(err.toString(), err.toString().contains("="));
117
    }
118
119
}
(-)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
@Keys("general=stuff")
44
package org.netbeans.modules.openide.util;
45
import org.openide.util.NbBundle.Keys;

Return to bug 192750