3050N/A/*
3050N/A * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
3050N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3050N/A *
3050N/A * This code is free software; you can redistribute it and/or modify it
3050N/A * under the terms of the GNU General Public License version 2 only, as
3050N/A * published by the Free Software Foundation.
3050N/A *
3050N/A * This code is distributed in the hope that it will be useful, but WITHOUT
3050N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
3050N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
3050N/A * version 2 for more details (a copy is included in the LICENSE file that
3050N/A * accompanied this code).
3050N/A *
3050N/A * You should have received a copy of the GNU General Public License version
3050N/A * 2 along with this work; if not, write to the Free Software Foundation,
3050N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
3050N/A *
3050N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
3050N/A * or visit www.oracle.com if you need additional information or have any
3050N/A * questions.
3050N/A */
3050N/A
3050N/Aimport java.io.BufferedReader;
3050N/Aimport java.io.File;
3050N/Aimport java.io.FileInputStream;
3050N/Aimport java.io.FileOutputStream;
3050N/Aimport java.io.InputStreamReader;
3050N/Aimport java.io.PrintWriter;
3050N/Aimport java.util.HashMap;
3050N/Aimport java.util.HashSet;
3050N/Aimport java.util.ListResourceBundle;
3050N/Aimport java.util.Map;
3050N/Aimport java.util.Set;
3050N/A
3050N/A/**
3050N/A * Prepares new key names for Resources.java.
3050N/A * 6987827: security/util/Resources.java needs improvement
3050N/A *
3050N/A * Run inside jdk/src/share/classes:
3050N/A *
3050N/A * java NewResourcesNames $(
3050N/A * for a in $(find com/sun/security sun/security javax/security -type f); do
3050N/A * egrep -q '(ResourcesMgr.getString|rb.getString)' $a && echo $a
3050N/A * done)
3050N/A *
3050N/A * Before running this tool, run the following two commands to make sure there
3050N/A * are only these 2 types of calls into the resources:
3050N/A * for a in `find com/sun/security sun/security javax/security -type f`; do
3050N/A * cat $a | perl -ne 'print if /\bResourcesMgr\b/'; done |
3050N/A * grep -v ResourcesMgr.getString
3050N/A * for a in `find com/sun/security sun/security -type f`; do
3050N/A * cat $a | perl -ne 'print if /\brb\b/'; done |
3050N/A * grep -v rb.getString
3050N/A */
3050N/Aclass NewResourcesNames {
3050N/A
3050N/A // Max length of normalized names
3050N/A static int MAXLEN = 127;
3050N/A
3050N/A static String[] resources = {
3050N/A "sun/security/tools/JarSignerResources.java",
3050N/A "sun/security/util/Resources.java",
3050N/A "sun/security/util/AuthResources.java",
3050N/A };
3050N/A
3050N/A public static void main(String[] args) throws Exception {
3050N/A
3050N/A // Load all names inside resources files
3050N/A Map<String,String> allnames = loadResources();
3050N/A
3050N/A // Modify the callers. There are two patterns:
3050N/A // 1. ResourcesMgr.getString("
3050N/A // used by most JAAS codes
3050N/A // 2. rb.getString("
3050N/A // used by tools
3050N/A Set<String> allfound = new HashSet<String>();
3050N/A for (String arg: args) {
3050N/A allfound.addAll(rewriteFile(arg, "ResourcesMgr.getString(\""));
3050N/A allfound.addAll(rewriteFile(arg, "rb.getString(\""));
3050N/A }
3050N/A
3050N/A // Special case 1: KeyTool's enum definition of commands and options
3050N/A allfound.addAll(keyToolEnums());
3050N/A
3050N/A // Special case 2: PolicyFile called this 4 times
3050N/A allfound.addAll(rewriteFile("sun/security/provider/PolicyFile.java",
3050N/A "ResourcesMgr.getString(POLICY+\""));
3050N/A
3050N/A // During the calls above, you can read sth like:
3050N/A //
3050N/A // Working on com/sun/security/auth/PolicyParser.java
3050N/A // GOOD match is 17
3050N/A //
3050N/A // This means a " exists right after getString(. Sometimes you see
3050N/A //
3050N/A // Working on sun/security/tools/KeyTool.java
3050N/A // BAD!! pmatch != match: 212 != 209
3050N/A // Working on sun/security/provider/PolicyFile.java
3050N/A // BAD!! pmatch != match: 14 != 10
3050N/A //
3050N/A // which is mismatch. There are only two such special cases list above.
3050N/A // For KeyTool, there are 3 calls for showing help. For PolicyTool, 3
3050N/A // for name prefixed with POLICY. They are covered in the two special
3050N/A // cases above.
3050N/A
3050N/A // Names used but not defined. This is mostly error, except for
3050N/A // special case 2 above. So it's OK to see 3 entries red here
3050N/A if (!allnames.keySet().containsAll(allfound)) {
3050N/A err("FATAL: Undefined names");
3050N/A for (String name: allfound) {
3050N/A if (!allnames.keySet().contains(name)) {
3050N/A err(" " + name);
3050N/A }
3050N/A }
3050N/A }
3050N/A
3050N/A // Names defined but not used. Mostly this is old entries not removed.
3050N/A // When soemone remove a line of code, he dares not remove the entry
3050N/A // in case it's also used somewhere else.
3050N/A if (!allfound.containsAll(allnames.keySet())) {
3050N/A System.err.println("WARNING: Unused names");
3050N/A for (String name: allnames.keySet()) {
3050N/A if (!allfound.contains(name)) {
3050N/A System.err.println(allnames.get(name));
3050N/A System.err.println(" " + normalize(name));
3050N/A System.err.println(" [" + name + "]");
3050N/A }
3050N/A }
3050N/A }
3050N/A }
3050N/A
3050N/A
3050N/A /**
3050N/A * Loads the three resources files. Saves names into a Map.
3050N/A */
3050N/A private static Map<String,String> loadResources() throws Exception {
3050N/A
3050N/A // Name vs Resource
3050N/A Map<String,String> allnames = new HashMap<String,String>();
3050N/A
3050N/A for (String f: resources) {
3050N/A String clazz =
3050N/A f.replace('/', '.').substring(0, f.length()-5);
3050N/A
3050N/A Set<String> expected = loadClass(clazz);
3050N/A Set<String> found = rewriteFile(f, "{\"");
3050N/A
3050N/A // This is to check that word parsing is identical to Java thinks
3050N/A if (!expected.equals(found)) {
3050N/A throw new Exception("Expected and found do not match");
3050N/A }
3050N/A
3050N/A for (String name: found) {
3050N/A allnames.put(name, f);
3050N/A }
3050N/A }
3050N/A return allnames;
3050N/A }
3050N/A
3050N/A /**
3050N/A * Special case treat for enums description in KeyTool
3050N/A */
3050N/A private static Set<String> keyToolEnums() throws Exception {
3050N/A
3050N/A Set<String> names = new HashSet<String>();
3050N/A
3050N/A String file = "sun/security/tools/KeyTool.java";
3050N/A System.err.println("Working on " + file);
3050N/A File origFile = new File(file);
3050N/A File tmpFile = new File(file + ".tmp");
3050N/A origFile.renameTo(tmpFile);
3050N/A tmpFile.deleteOnExit();
3050N/A
3050N/A BufferedReader br = new BufferedReader(
3050N/A new InputStreamReader(new FileInputStream(tmpFile)));
3050N/A PrintWriter out = new PrintWriter(new FileOutputStream(origFile));
3050N/A
3050N/A int stage = 0; // 1. commands, 2. options, 3. finished
3050N/A int match = 0;
3050N/A
3050N/A while (true) {
3050N/A String s = br.readLine();
3050N/A if (s == null) {
3050N/A break;
3050N/A }
3050N/A if (s.indexOf("enum Command") >= 0) stage = 1;
3050N/A else if (s.indexOf("enum Option") >= 0) stage = 2;
3050N/A else if (s.indexOf("private static final String JKS") >= 0) stage = 3;
3050N/A
3050N/A if (stage == 1 || stage == 2) {
3050N/A if (s.indexOf("(\"") >= 0) {
3050N/A match++;
3050N/A int p1, p2;
3050N/A if (stage == 1) {
3050N/A p1 = s.indexOf("\"");
3050N/A p2 = s.indexOf("\"", p1+1);
3050N/A } else {
3050N/A p2 = s.lastIndexOf("\"");
3050N/A p1 = s.lastIndexOf("\"", p2-1);
3050N/A }
3050N/A String name = s.substring(p1+1, p2);
3050N/A names.add(name);
3050N/A out.println(s.substring(0, p1+1) +
3050N/A normalize(name) +
3050N/A s.substring(p2));
3050N/A } else {
3050N/A out.println(s);
3050N/A }
3050N/A } else {
3050N/A out.println(s);
3050N/A }
3050N/A }
3050N/A br.close();
3050N/A out.close();
3050N/A System.err.println(" GOOD match is " + match);
3050N/A return names;
3050N/A }
3050N/A
3050N/A /**
3050N/A * Loads a resources using JRE and returns the names
3050N/A */
3050N/A private static Set<String> loadClass(String clazz) throws Exception {
3050N/A ListResourceBundle lrb =
3050N/A (ListResourceBundle)Class.forName(clazz).newInstance();
3050N/A Set<String> keys = lrb.keySet();
3050N/A Map<String,String> newold = new HashMap<String,String>();
3050N/A boolean dup = false;
3050N/A // Check if normalize() creates dup entries. This is crucial.
3050N/A for (String k: keys) {
3050N/A String key = normalize(k);
3050N/A if (newold.containsKey(key)) {
3050N/A err("Dup found for " + key + ":");
3050N/A err("["+newold.get(key)+"]");
3050N/A err("["+k+"]");
3050N/A dup = true;
3050N/A }
3050N/A newold.put(key, k);
3050N/A }
3050N/A if (dup) throw new Exception();
3050N/A return keys;
3050N/A }
3050N/A
3050N/A /**
3050N/A * Rewrites a file using a pattern. The name string should be right after
3050N/A * the pattern. Note: pattern ignores whitespaces. Returns names found.
3050N/A */
3050N/A private static Set<String> rewriteFile(String file, String pattern)
3050N/A throws Exception {
3050N/A
3050N/A System.err.println("Working on " + file);
3050N/A Set<String> names = new HashSet<String>();
3050N/A
3050N/A int plen = pattern.length();
3050N/A int match = 0;
3050N/A
3050N/A // The bare XXX.getString is also matched. Sometimes getString is
3050N/A // called but does not use literal strings. This is harder to solve.
3050N/A
3050N/A int pmatch = 0;
3050N/A int pheadlen = plen - 2;
3050N/A String phead = pattern.substring(0, plen-2);
3050N/A
3050N/A // The non-whitespace chars read since, used to check for pattern
3050N/A StringBuilder history = new StringBuilder();
3050N/A int hlen = 0;
3050N/A
3050N/A File origFile = new File(file);
3050N/A File tmpFile = new File(file + ".tmp");
3050N/A origFile.renameTo(tmpFile);
3050N/A tmpFile.deleteOnExit();
3050N/A
3050N/A FileInputStream fis = new FileInputStream(tmpFile);
3050N/A FileOutputStream fos = new FileOutputStream(origFile);
3050N/A
3050N/A while (true) {
3050N/A int ch = fis.read();
3050N/A if (ch < 0) break;
3050N/A if (!Character.isWhitespace(ch)) {
3050N/A history.append((char)ch);
3050N/A hlen++;
3050N/A if (pheadlen > 0 && hlen >= pheadlen &&
3050N/A history.substring(hlen-pheadlen, hlen).equals(phead)) {
3050N/A pmatch++;
3050N/A }
3050N/A }
3050N/A
3050N/A if (hlen >= plen &&
3050N/A history.substring(hlen-plen, hlen).equals(pattern)) {
3050N/A match++;
3050N/A history = new StringBuilder();
3050N/A hlen = 0;
3050N/A
3050N/A fos.write(ch);
3050N/A
3050N/A // Save a name
3050N/A StringBuilder sb = new StringBuilder();
3050N/A // Save things after the second ". Maybe it's an end, maybe
3050N/A // it's just literal string concatenation.
3050N/A StringBuilder tail = new StringBuilder();
3050N/A
3050N/A boolean in = true; // inside name string
3050N/A while (true) {
3050N/A int n = fis.read();
3050N/A if (in) {
3050N/A if (n == '\\') {
3050N/A int second = fis.read();
3050N/A switch (second) {
3050N/A case 'n': sb.append('\n'); break;
3050N/A case 'r': sb.append('\r'); break;
3050N/A case 't': sb.append('\t'); break;
3050N/A case '"': sb.append('"'); break;
3050N/A default: throw new Exception(String.format(
3050N/A "I don't know this escape: %s%c",
3050N/A sb.toString(), second));
3050N/A }
3050N/A } else if (n == '"') {
3050N/A in = false;
3050N/A // Maybe string concat? say bytes until clear
3050N/A tail = new StringBuilder();
3050N/A tail.append('"');
3050N/A } else {
3050N/A sb.append((char)n);
3050N/A }
3050N/A } else {
3050N/A tail.append((char)n);
3050N/A if (n == '"') { // string concat, in again
3050N/A in = true;
3050N/A } else if (n == ',' || n == ')') { // real end
3050N/A break;
3050N/A } else if (Character.isWhitespace(n) || n == '+') {
3050N/A // string concat
3050N/A } else {
3050N/A throw new Exception("Not a correct concat");
3050N/A }
3050N/A }
3050N/A }
3050N/A String s = sb.toString();
3050N/A names.add(s);
3050N/A fos.write(normalize(s).getBytes());
3050N/A fos.write(tail.toString().getBytes());
3050N/A } else {
3050N/A fos.write(ch);
3050N/A }
3050N/A }
3050N/A
3050N/A // Check pheadlen > 0. Don't want to mess with rewrite for resources
3050N/A if (pheadlen > 0 && pmatch != match) {
3050N/A err(" BAD!! pmatch != match: " + pmatch + " != " + match);
3050N/A } else {
3050N/A System.err.println(" GOOD match is " + match);
3050N/A }
3050N/A
3050N/A fis.close();
3050N/A fos.close();
3050N/A return names;
3050N/A }
3050N/A
3050N/A /**
3050N/A * Normalize a string. Rules:
3050N/A *
3050N/A * 1. If all spacebar return "nSPACE", n is count
3050N/A * 2. If consisting at least one alphanumeric:
3050N/A * a. All alphanumeric remain
3050N/A * b. All others in a row goes to a single ".", even if at head or tail
3050N/A * 3. Otherwise:
3050N/A * a. "****\n\n" to "STARNN", special case
3050N/A * b. the English name if first char in *,.\n():'"
3050N/A *
3050N/A * Current observations show there's no dup, Hurray! Otherwise, add more
3050N/A * special cases.
3050N/A */
3050N/A private static String normalize(String s) throws Exception {
3050N/A boolean needDot = false;
3050N/A
3050N/A // All spacebar case
3050N/A int n = 0;
3050N/A for (char c: s.toCharArray()) {
3050N/A if (c == ' ') n++;
3050N/A else n = -10000;
3050N/A }
3050N/A if (n == 1) return "SPACE";
3050N/A else if (n > 1) return "" + n + "SPACE";
3050N/A
3050N/A StringBuilder sb = new StringBuilder();
3050N/A int dotpos = -1;
3050N/A for (int i=0; i<s.length(); i++) {
3050N/A char c = s.charAt(i);
3050N/A if (Character.isLetter(c) || Character.isDigit(c) ||
3050N/A c == '{' || c == '}') {
3050N/A if (needDot) {
3050N/A // Rememeber the last dot, we want shorter form nice
3050N/A if (sb.length() <= MAXLEN) dotpos = sb.length();
3050N/A // "." only added when an alphanumeric is seen. This makes
3050N/A // sure sb is empty when there's no alphanumerics at all
3050N/A sb.append(".");
3050N/A }
3050N/A sb.append(c);
3050N/A needDot = false;
3050N/A } else {
3050N/A needDot = true;
3050N/A }
3050N/A }
3050N/A
3050N/A // No alphanemeric?
3050N/A if (sb.length() == 0) {
3050N/A if (s.contains("*") && s.contains("\n")) {
3050N/A return "STARNN";
3050N/A }
3050N/A for (char c: s.toCharArray()) {
3050N/A switch (c) {
3050N/A case '*': return "STAR";
3050N/A case ',': return "COMMA";
3050N/A case '.': return "PERIOD";
3050N/A case '\n': return "NEWLINE";
3050N/A case '(': return "LPARAM";
3050N/A case ')': return "RPARAM";
3050N/A case ':': return "COLON";
3050N/A case '\'': case '"': return "QUOTE";
3050N/A }
3050N/A }
3050N/A throw new Exception("Unnamed char: [" + s + "]");
3050N/A }
3050N/A
3050N/A // tail "." only added when there are alphanumerics
3050N/A if (needDot) sb.append('.');
3050N/A String res = sb.toString();
3050N/A if (res.length() > MAXLEN) {
3050N/A if (dotpos < 0) throw new Exception("No dot all over? " + s);
3050N/A return res.substring(0, dotpos);
3050N/A } else {
3050N/A return res;
3050N/A }
3050N/A }
3050N/A
3050N/A private static void err(String string) {
3050N/A System.out.println("\u001b[1;37;41m" + string + "\u001b[m");
3050N/A }
3050N/A}