PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/projects/netbeans-7.3/openide.util/src/org/netbeans/modules/openide/util/NbBundleProcessor.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 405 lines | 340 code | 12 blank | 53 comment | 65 complexity | a64147d584462f6b443baf1cd13a0929 MD5 | raw file
  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. package org.netbeans.modules.openide.util;
  43. import java.io.IOException;
  44. import java.io.InputStream;
  45. import java.io.OutputStream;
  46. import java.io.PrintWriter;
  47. import java.io.Writer;
  48. import java.util.ArrayList;
  49. import java.util.Collection;
  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 java.util.TreeMap;
  57. import java.util.TreeSet;
  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.AnnotationMirror;
  66. import javax.lang.model.element.AnnotationValue;
  67. import javax.lang.model.element.Element;
  68. import javax.lang.model.element.ElementKind;
  69. import javax.lang.model.element.ExecutableElement;
  70. import javax.lang.model.element.PackageElement;
  71. import javax.lang.model.element.TypeElement;
  72. import javax.tools.Diagnostic.Kind;
  73. import javax.tools.StandardLocation;
  74. import org.openide.util.EditableProperties;
  75. import org.openide.util.NbBundle;
  76. import org.openide.util.NbCollections;
  77. import org.openide.util.Utilities;
  78. import org.openide.util.lookup.ServiceProvider;
  79. @ServiceProvider(service = Processor.class)
  80. @SupportedSourceVersion(SourceVersion.RELEASE_6)
  81. public class NbBundleProcessor extends AbstractProcessor {
  82. public @Override Set<String> getSupportedAnnotationTypes() {
  83. return Collections.singleton(NbBundle.Messages.class.getCanonicalName());
  84. }
  85. public @Override boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  86. if (roundEnv.processingOver()) {
  87. return false;
  88. }
  89. Map</*package*/String,Set<Element>> annotatedElementsByPackage = new HashMap<String,Set<Element>>();
  90. for (Element e : roundEnv.getElementsAnnotatedWith(NbBundle.Messages.class)) {
  91. NbBundle.Messages messages = e.getAnnotation(NbBundle.Messages.class);
  92. if (messages == null) { // bug in java.source, apparently; similar to #195983
  93. continue;
  94. }
  95. String pkg = findPackage(e);
  96. Set<Element> annotatedElements = annotatedElementsByPackage.get(pkg);
  97. if (annotatedElements == null) {
  98. annotatedElements = new HashSet<Element>();
  99. annotatedElementsByPackage.put(pkg, annotatedElements);
  100. }
  101. annotatedElements.add(e);
  102. }
  103. PACKAGE: for (Map.Entry<String,Set<Element>> packageEntry : annotatedElementsByPackage.entrySet()) {
  104. String pkg = packageEntry.getKey();
  105. Set<Element> annotatedElements = packageEntry.getValue();
  106. PackageElement pkgE = processingEnv.getElementUtils().getPackageElement(pkg);
  107. if (pkgE != null) {
  108. Set<Element> unscannedTopElements = new HashSet<Element>();
  109. unscannedTopElements.add(pkgE);
  110. try {
  111. unscannedTopElements.addAll(pkgE.getEnclosedElements());
  112. } catch (/*NullPointerException,BadClassFile*/RuntimeException x) { // #196556
  113. processingEnv.getMessager().printMessage(Kind.WARNING, "#196556: reading " + pkg + " failed with " + x + " in " + x.getStackTrace()[0] + "; do a clean build!");
  114. }
  115. unscannedTopElements.removeAll(roundEnv.getRootElements());
  116. addToAnnotatedElements(unscannedTopElements, annotatedElements);
  117. } else {
  118. processingEnv.getMessager().printMessage(Kind.WARNING, "Could not check for other source files in " + pkg);
  119. }
  120. Map</*key*/String,/*value*/String> pairs = new HashMap<String,String>();
  121. Map</*identifier*/String,Element> identifiers = new HashMap<String,Element>();
  122. Map</*key*/String,/*simplename*/String> compilationUnits = new HashMap<String,String>();
  123. Map</*key*/String,/*line*/String[]> comments = new HashMap<String,String[]>();
  124. for (Element e : annotatedElements) {
  125. String simplename = findCompilationUnitName(e);
  126. List<String> runningComments = new ArrayList<String>();
  127. for (String keyValue : e.getAnnotation(NbBundle.Messages.class).value()) {
  128. if (keyValue.startsWith("#")) {
  129. runningComments.add(keyValue);
  130. if (keyValue.matches("# +(PART)?(NO)?I18N *")) {
  131. processingEnv.getMessager().printMessage(Kind.ERROR, "#NOI18N and related keywords must not include spaces", e);
  132. }
  133. continue;
  134. }
  135. int i = keyValue.indexOf('=');
  136. if (i == -1) {
  137. processingEnv.getMessager().printMessage(Kind.ERROR, "Bad key=value: " + keyValue, e);
  138. continue;
  139. }
  140. String key = keyValue.substring(0, i);
  141. if (key.isEmpty() || !key.equals(key.trim())) {
  142. processingEnv.getMessager().printMessage(Kind.ERROR, "Whitespace not permitted in key: " + keyValue, e);
  143. continue;
  144. }
  145. Element original = identifiers.put(toIdentifier(key), e);
  146. if (original != null) {
  147. processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate key: " + key, e);
  148. processingEnv.getMessager().printMessage(Kind.ERROR, "Duplicate key: " + key, original);
  149. continue PACKAGE; // do not generate anything
  150. }
  151. String value = keyValue.substring(i + 1);
  152. pairs.put(key, value);
  153. compilationUnits.put(key, simplename);
  154. if (!runningComments.isEmpty()) {
  155. comments.put(key, runningComments.toArray(new String[runningComments.size()]));
  156. runningComments.clear();
  157. }
  158. }
  159. if (!runningComments.isEmpty()) {
  160. processingEnv.getMessager().printMessage(Kind.ERROR, "Comments must precede keys", e);
  161. }
  162. }
  163. Element[] elements = new HashSet<Element>(identifiers.values()).toArray(new Element[0]);
  164. try {
  165. EditableProperties p = new EditableProperties(true);
  166. // Load any preexisting bundle so we can just add our keys.
  167. try {
  168. InputStream is = processingEnv.getFiler().getResource(StandardLocation.SOURCE_PATH, pkg, "Bundle.properties").openInputStream();
  169. try {
  170. p.load(is);
  171. } finally {
  172. is.close();
  173. }
  174. } catch (IOException x) {
  175. // OK, not there
  176. }
  177. for (String key : p.keySet()) {
  178. if (pairs.containsKey(key)) {
  179. processingEnv.getMessager().printMessage(Kind.ERROR, "Key " + key + " is a duplicate of one from Bundle.properties", identifiers.get(toIdentifier(key)));
  180. }
  181. }
  182. // Also check class output for (1) incremental builds, (2) preexisting bundles from Maven projects.
  183. try {
  184. InputStream is = processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, "Bundle.properties").openInputStream();
  185. try {
  186. // do not use p.load(is) as the impl in EditableProperties does not currently handle duplicates properly
  187. EditableProperties p2 = new EditableProperties(true);
  188. p2.load(is);
  189. p.putAll(p2);
  190. } finally {
  191. is.close();
  192. }
  193. } catch (IOException x) {
  194. // OK, not there
  195. }
  196. p.putAll(pairs);
  197. for (Map.Entry<String,String[]> entry2 : comments.entrySet()) {
  198. p.setComment(entry2.getKey(), entry2.getValue(), false);
  199. }
  200. OutputStream os = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, pkg, "Bundle.properties", elements).openOutputStream();
  201. try {
  202. p.store(os);
  203. } finally {
  204. os.close();
  205. }
  206. Map</*identifier*/String,/*method body*/String> methods = new TreeMap<String,String>();
  207. for (Map.Entry<String, String> entry2 : pairs.entrySet()) {
  208. String key = entry2.getKey();
  209. String value = entry2.getValue();
  210. StringBuilder method = new StringBuilder();
  211. method.append(" /**\n");
  212. List<String> params = new ArrayList<String>();
  213. int i = 0;
  214. while (value.contains("{" + i)) {
  215. params.add("arg" + i++);
  216. }
  217. String[] commentLines = comments.get(key);
  218. if (commentLines != null) {
  219. for (String comment : commentLines) {
  220. Matcher m = Pattern.compile("# [{](\\d+)[}] - (.+)").matcher(comment);
  221. if (m.matches()) {
  222. i = Integer.parseInt(m.group(1));
  223. while (i >= params.size()) {
  224. params.add("arg" + params.size());
  225. }
  226. String desc = m.group(2);
  227. params.set(i, toIdentifier(desc));
  228. method.append(" * @param ").append(params.get(i)).append(" ").append(toJavadoc(desc)).append("\n");
  229. }
  230. }
  231. }
  232. StringBuffer annotatedValue = new StringBuffer("<i>");
  233. Matcher m = Pattern.compile("[{](\\d+)[}]").matcher(toJavadoc(value));
  234. while (m.find()) {
  235. i = Integer.parseInt(m.group(1));
  236. m.appendReplacement(annotatedValue, i < params.size() ? "</i>{@code " + params.get(i) + "}<i>" : m.group());
  237. }
  238. m.appendTail(annotatedValue);
  239. annotatedValue.append("</i>");
  240. method.append(" * @return ").append(annotatedValue.toString().replace("<i></i>", "")).append('\n');
  241. method.append(" * @see ").append(compilationUnits.get(key)).append('\n');
  242. method.append(" */\n");
  243. String name = toIdentifier(key);
  244. method.append(" static String ").append(name).append("(");
  245. boolean first = true;
  246. i = 0;
  247. for (String param : params) {
  248. if (param.equals("arg" + i)) {
  249. warnUndocumented(i, identifiers.get(name), key);
  250. }
  251. i++;
  252. if (first) {
  253. first = false;
  254. } else {
  255. method.append(", ");
  256. }
  257. method.append("Object ").append(param);
  258. }
  259. method.append(") {\n");
  260. method.append(" return org.openide.util.NbBundle.getMessage(Bundle.class, \"").append(key).append("\"");
  261. for (String param : params) {
  262. method.append(", ").append(param);
  263. }
  264. method.append(");\n");
  265. method.append(" }\n");
  266. methods.put(name, method.toString());
  267. }
  268. try {
  269. Set<String> restored = new TreeSet<String>();
  270. Matcher m = Pattern.compile(" /[*][*]\r?\n(?: [*].+\r?\n)+ [*] @see (?:[\\w-]+)\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));
  271. while (m.find()) {
  272. String identifier = m.group(1);
  273. if (!methods.containsKey(identifier)) {
  274. methods.put(identifier, m.group());
  275. restored.add(identifier);
  276. }
  277. }
  278. /*
  279. if (!restored.isEmpty()) {
  280. processingEnv.getMessager().printMessage(Kind.NOTE, "loaded " + pkg + ".Bundle identifiers " + restored + " from earlier run");
  281. }
  282. */
  283. } catch (IOException x) {
  284. // OK, not there
  285. }
  286. String fqn = pkg + ".Bundle";
  287. Writer w = processingEnv.getFiler().createSourceFile(fqn, elements).openWriter();
  288. try {
  289. PrintWriter pw = new PrintWriter(w);
  290. pw.println("package " + pkg + ";");
  291. pw.println("/** Localizable strings for {@link " + pkg + "}. */");
  292. pw.println("@javax.annotation.Generated(value=\"" + NbBundleProcessor.class.getName() + "\")");
  293. pw.println("class Bundle {");
  294. for (String method : methods.values()) {
  295. pw.print(method);
  296. }
  297. pw.println(" private void Bundle() {}");
  298. pw.println("}");
  299. pw.flush();
  300. pw.close();
  301. } finally {
  302. w.close();
  303. }
  304. } catch (IOException x) {
  305. processingEnv.getMessager().printMessage(Kind.ERROR, "Could not generate files: " + x, elements[0]);
  306. }
  307. }
  308. return true;
  309. }
  310. private String findPackage(Element e) {
  311. switch (e.getKind()) {
  312. case PACKAGE:
  313. return ((PackageElement) e).getQualifiedName().toString();
  314. default:
  315. return findPackage(e.getEnclosingElement());
  316. }
  317. }
  318. private String findCompilationUnitName(Element e) {
  319. switch (e.getKind()) {
  320. case PACKAGE:
  321. return "package-info";
  322. case CLASS:
  323. case INTERFACE:
  324. case ENUM:
  325. case ANNOTATION_TYPE:
  326. switch (e.getEnclosingElement().getKind()) {
  327. case PACKAGE:
  328. return e.getSimpleName().toString();
  329. }
  330. }
  331. return findCompilationUnitName(e.getEnclosingElement());
  332. }
  333. private String toIdentifier(String key) {
  334. if (Utilities.isJavaIdentifier(key)) {
  335. return key;
  336. } else {
  337. String i = key.replaceAll("[^\\p{javaJavaIdentifierPart}]+", "_");
  338. if (Utilities.isJavaIdentifier(i)) {
  339. return i;
  340. } else {
  341. return "_" + i;
  342. }
  343. }
  344. }
  345. private String toJavadoc(String text) {
  346. return text.replace("&", "&amp;").replace("<", "&lt;").replace("*/", "&#x2A;/").replace("\n", "<br>").replace("@", "&#64;");
  347. }
  348. private void addToAnnotatedElements(Collection<? extends Element> unscannedElements, Set<Element> annotatedElements) {
  349. for (Element e : unscannedElements) {
  350. if (e.getAnnotation(NbBundle.Messages.class) != null) {
  351. annotatedElements.add(e);
  352. }
  353. if (e.getKind() != ElementKind.PACKAGE) {
  354. addToAnnotatedElements(e.getEnclosedElements(), annotatedElements);
  355. }
  356. }
  357. }
  358. private void warnUndocumented(int i, Element e, String key) {
  359. AnnotationMirror mirror = null;
  360. AnnotationValue value = null;
  361. if (e != null) {
  362. for (AnnotationMirror _mirror : e.getAnnotationMirrors()) {
  363. if (_mirror.getAnnotationType().toString().equals(NbBundle.Messages.class.getCanonicalName())) {
  364. mirror = _mirror;
  365. for (Map.Entry<? extends ExecutableElement,? extends AnnotationValue> entry : mirror.getElementValues().entrySet()) {
  366. if (entry.getKey().getSimpleName().contentEquals("value")) {
  367. // SimpleAnnotationValueVisitor6 unusable here since we need to determine the AnnotationValue in scope when visitString is called:
  368. Object v = entry.getValue().getValue();
  369. if (v instanceof String) {
  370. if (((String) v).startsWith(key + "=")) {
  371. value = entry.getValue();
  372. }
  373. } else {
  374. for (AnnotationValue subentry : NbCollections.checkedListByCopy((List<?>) v, AnnotationValue.class, true)) {
  375. v = subentry.getValue();
  376. if (v instanceof String) {
  377. if (((String) v).startsWith(key + "=")) {
  378. value = subentry;
  379. break;
  380. }
  381. }
  382. }
  383. }
  384. break;
  385. }
  386. }
  387. break;
  388. }
  389. }
  390. }
  391. processingEnv.getMessager().printMessage(Kind.WARNING, "Undocumented format parameter {" + i + "}", e, mirror, value);
  392. }
  393. }