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