Path: blob/master/test/jdk/javax/xml/jaxp/Encodings/CheckEncodingPropertiesFile.java
41153 views
/*1* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/**24* @test25* @bug 8008738 806513826* @summary checks that the mapping implemented by27* com.sun.org.apache.xml.internal.serializer.Encodings28* correctly identifies valid Charset names and29* correctly maps them to their preferred mime names.30* Also checks that the Encodings.properties resource file31* is consistent.32* @modules java.xml/com.sun.org.apache.xml.internal.serializer:+open33* @compile -XDignore.symbol.file CheckEncodingPropertiesFile.java34* @run main CheckEncodingPropertiesFile35* @author Daniel Fuchs36*/3738import com.sun.org.apache.xml.internal.serializer.EncodingInfo;39import com.sun.org.apache.xml.internal.serializer.Encodings;40import java.io.InputStreamReader;41import java.lang.reflect.Method;42import java.nio.charset.Charset;43import java.util.ArrayList;44import java.util.Arrays;45import java.util.Collection;46import java.util.Collections;47import java.util.HashMap;48import java.util.HashSet;49import java.util.LinkedHashSet;50import java.util.List;51import java.util.Map;52import java.util.Map.Entry;53import java.util.Properties;54import java.util.Set;55import java.util.StringTokenizer;5657public class CheckEncodingPropertiesFile {5859private static final String ENCODINGS_FILE = "com/sun/org/apache/xml/internal/serializer/Encodings.properties";6061public static void main(String[] args) throws Exception {62Properties props = new Properties();63Module xmlModule = EncodingInfo.class.getModule();64try (InputStreamReader is = new InputStreamReader(xmlModule.getResourceAsStream(ENCODINGS_FILE))) {65props.load(is);66}6768if (!props.containsKey("UTF8")) {69// If the test fails here - it may indicate that you stumbled on an70// issue similar to that fixed by JDK-8065138.71// Check that the content of the Encodings.properties included in72// the tested build image matches the content of the file in the source73// jaxp tree of the jdk forest.74throw new RuntimeException("UTF8 key missing in " + ENCODINGS_FILE);75}7677//printAllCharsets();7879test(props);80}818283private static final class CheckCharsetMapping {8485/**86* A map that maps Java or XML name to canonical charset names.87* key: upper cased value of Java or XML name.88* value: case-sensitive canonical name of charset.89*/90private final Map<String, String> charsetMap = new HashMap<>();9192private final Map<String, String> preferredMime = new HashMap<>();9394/**95* Unresolved alias names.96* For a given set of names pointing to the same unresolved charset,97* this map will contain, for each alias in the set, a mapping98* with the alias.toUpperValue() as key and the set of known aliases99* as value.100*/101private final Map<String, Collection<String>> unresolved = new HashMap<>();102103public final static class ConflictingCharsetError extends Error {104ConflictingCharsetError(String a, String cs1, String cs2) {105super("Conflicting charset mapping for '"+a+"': '"+cs1+"' and '"+cs2+"'");106}107}108109public final static class MissingValidCharsetNameError extends Error {110MissingValidCharsetNameError(String name, Collection<String> aliases) {111super(name+": Line "+aliases+" has no recognized charset alias");112}113}114115public final static class ConflictingPreferredMimeNameError extends Error {116ConflictingPreferredMimeNameError(String a, String cs1, String cs2) {117super("Conflicting preferred mime name for '"+a+"': '"+cs1+"' and '"+cs2+"'");118}119}120121/**122* For each alias in aliases, attempt to find the canonical123* charset name.124* All names in aliases are supposed to point to the same charset.125* Names in aliases can be java names or XML names, indifferently.126* @param aliases list of names (aliases) for a given charset.127* @return The canonical name of the charset, if found, null otherwise.128*/129private String findCharsetNameFor(String[] aliases) {130String cs = null;131String res = null;132for (String a : aliases) {133final String k = a.toUpperCase();134String cachedCs = charsetMap.get(k);135if (cs == null) {136cs = cachedCs;137}138if (cachedCs != null && cs != null139&& !Charset.forName(cachedCs).name().equals(Charset.forName(cs).name())) {140throw new ConflictingCharsetError(a,cs,cachedCs);141}142try {143final String rcs = Charset.forName(a).name();144if (cs != null && !Charset.forName(cs).name().equals(rcs)) {145throw new ConflictingCharsetError(a,cs,rcs);146}147if (res == null) {148if (a.equals(aliases[0])) {149res = a;150} else {151res = cs;152}153}154cs = rcs;155charsetMap.put(k, res == null ? cs : res);156} catch (Exception x) {157continue;158}159}160return res == null ? cs : res;161}162163/**164* Register a canonical charset name for a given set of aliases.165*166* @param charsetName the canonical charset name.167* @param aliases a list of aliases for the given charset.168*/169private void registerCharsetNameFor(String charsetName, String[] aliases) {170if (charsetName == null) throw new NullPointerException();171172for (String a : aliases) {173String k = a.toUpperCase();174String csv = charsetMap.get(k);175if (csv == null) {176charsetMap.put(k, charsetName);177csv = charsetName;178} else if (!csv.equals(charsetName)) {179throw new ConflictingCharsetError(a,charsetName,csv);180}181182final Collection<String> c = unresolved.get(k);183if (c != null) {184for (String aa : c) {185k = aa.toUpperCase();186String csvv = charsetMap.get(k);187if (csvv == null) charsetMap.put(k, csv);188unresolved.remove(k);189}190throw new MissingValidCharsetNameError(charsetName,c);191}192}193}194195/**196* Register a set of aliases as being unresolved.197* @param names the list of names - this should be what is returned by198* nameSet.toArray(new String[nameSet.size()])199* @param nameSet the set of unresolved aliases.200*/201private void registerUnresolvedNamesFor(String[] names, Collection<String> nameSet) {202// This is not necessarily an error: it could happen that some203// charsets are simply not supported on some OS/Arch204System.err.println("Warning: unresolved charset names: '"+ nameSet205+ "' This is not necessarily an error "206+ "- this charset may not be supported on this platform.");207for (String a : names) {208final String k = a.toUpperCase();209final Collection<String> c = unresolved.get(k);210if (c != null) {211//System.out.println("Found: "+a+" -> "+c);212//System.out.println("\t merging "+ c + " with " + nameSet);213nameSet.addAll(c);214for (String aa : c) {215unresolved.put(aa.toUpperCase(), nameSet);216}217}218unresolved.put(k, nameSet);219}220}221222223/**224* Add a new charset name mapping225* @param javaName the (supposedly) java name of the charset.226* @param xmlNames a list of corresponding XML names for that charset.227*/228void addMapping(String javaName, Collection<String> xmlNames) {229final LinkedHashSet<String> aliasNames = new LinkedHashSet<>();230aliasNames.add(javaName);231aliasNames.addAll(xmlNames);232final String[] aliases = aliasNames.toArray(new String[aliasNames.size()]);233final String cs = findCharsetNameFor(aliases);234if (cs != null) {235registerCharsetNameFor(cs, aliases);236if (xmlNames.size() > 0) {237String preferred = xmlNames.iterator().next();238String cachedPreferred = preferredMime.get(cs.toUpperCase());239if (cachedPreferred != null && !cachedPreferred.equals(preferred)) {240throw new ConflictingPreferredMimeNameError(cs, cachedPreferred, preferred);241}242preferredMime.put(cs.toUpperCase(), preferred);243}244} else {245registerUnresolvedNamesFor(aliases, aliasNames);246}247}248249/**250* Returns the canonical name of the charset for the given Java or XML251* alias name.252* @param alias the alias name253* @return the canonical charset name - or null if unknown.254*/255public String getCharsetNameFor(String alias) {256return charsetMap.get(alias.toUpperCase());257}258259}260261public static void test(Properties props) throws Exception {262263// First, build a mapping from the properties read from the resource264// file.265// We're going to check the consistency of the resource file266// while building this mapping, and throw errors if the file267// does not meet our assumptions.268//269Map<String, Collection<String>> lines = new HashMap<>();270final CheckCharsetMapping mapping = new CheckCharsetMapping();271272for (String key : props.stringPropertyNames()) {273Collection<String> values = getValues(props.getProperty(key));274lines.put(key, values);275mapping.addMapping(key, values);276}277278// Then build maps of EncodingInfos, and print along debugging279// information that should help understand the content of the280// resource file and the mapping it defines.281//282Map<String, EncodingInfo> javaInfos = new HashMap<>(); // Map indexed by java names283Map<String, EncodingInfo> xmlMap = new HashMap<>(); // Map indexed by XML names284Map<String, String> preferred =285new HashMap<>(mapping.preferredMime); // Java Name -> Preferred Mime Name286List<EncodingInfo> all = new ArrayList<>(); // unused...287for (Entry<String, Collection<String>> e : lines.entrySet()) {288final String charsetName = mapping.getCharsetNameFor(e.getKey());289if (charsetName == null) {290System.out.println("!! No charset for: "+e.getKey()+ " "+ e.getValue());291continue;292}293Charset c = Charset.forName(charsetName);294EncodingInfo info;295final String k = e.getKey().toUpperCase();296final String kc = charsetName.toUpperCase();297StringBuilder sb = new StringBuilder();298for (String xml : e.getValue()) {299final String kx = xml.toUpperCase();300info = xmlMap.get(kx);301if (info == null) {302info = new EncodingInfo(xml, charsetName);303System.out.println("** XML: "+xml+" -> "+charsetName);304xmlMap.put(kx, info);305all.add(info);306}307if (!javaInfos.containsKey(k)) {308javaInfos.put(k, info);309if (!preferred.containsKey(k)) {310preferred.put(k, xml);311}312sb.append("** Java: ").append(k).append(" -> ")313.append(xml).append(" (charset: ")314.append(charsetName).append(")\n");315}316if (!javaInfos.containsKey(kc)) {317if (!preferred.containsKey(kc)) {318preferred.put(kc, xml);319}320javaInfos.put(kc, info);321sb.append("** Java: ").append(kc).append(" -> ")322.append(xml).append(" (charset: ")323.append(charsetName).append(")\n");324}325if (!javaInfos.containsKey(c.name().toUpperCase())) {326if (!preferred.containsKey(c.name().toUpperCase())) {327preferred.put(c.name().toUpperCase(), xml);328}329javaInfos.put(c.name().toUpperCase(), info);330sb.append("** Java: ").append(c.name().toUpperCase()).append(" -> ")331.append(xml).append(" (charset: ")332.append(charsetName).append(")\n");333}334}335if (sb.length() == 0) {336System.out.println("Nothing new for "+charsetName+": "+e.getKey()+" -> "+e.getValue());337} else {338System.out.print(sb);339}340341}342343// Now we're going to verify that Encodings.java has done its job344// correctly. We're going to ask Encodings to convert java names to mime345// names and mime names to java names - and verify that the returned346// java names do map to recognized charsets.347//348// We're also going to verify that Encodings has recorded the preferred349// mime name correctly.350351Method m = Encodings.class.getDeclaredMethod("getMimeEncoding", String.class);352m.setAccessible(true);353354Set<String> xNames = new HashSet<>();355Set<String> jNames = new HashSet<>();356for (String name: xmlMap.keySet()) {357final String javaName = checkConvertMime2Java(name);358checkPreferredMime(m, javaName, preferred);359jNames.add(javaName);360xNames.add(name);361}362363364for (String javaName : lines.keySet()) {365final String javaCharsetName = mapping.getCharsetNameFor(javaName.toUpperCase());366if (javaCharsetName == null) continue;367if (!jNames.contains(javaName)) {368checkPreferredMime(m, javaName, preferred);369jNames.add(javaName);370}371for (String xml : lines.get(javaName)) {372if (xNames.contains(xml)) continue;373final String jName = checkConvertMime2Java(xml);374xNames.add(xml);375if (jNames.contains(jName)) continue;376checkPreferredMime(m, jName, preferred);377}378}379}380381private static String checkConvertMime2Java(String xml) {382final String jName = Encodings.convertMime2JavaEncoding(xml);383final String jCharsetName;384try {385jCharsetName = Charset.forName(jName).name();386} catch (Exception x) {387throw new Error("Unrecognized charset returned by Encodings.convertMime2JavaEncoding(\""+xml+"\")", x);388}389System.out.println("Encodings.convertMime2JavaEncoding(\""+xml+"\") = \""+jName+"\" ("+jCharsetName+")");390return jName;391}392393private static void checkPreferredMime(Method m, String javaName, Map<String,String> preferred)394throws Exception {395final String mime = (String) m.invoke(null, javaName);396final String expected = preferred.get(javaName.toUpperCase());397if (Arrays.deepEquals(new String[] {mime}, new String[] {expected})) {398System.out.println("Encodings.getMimeEncoding(\""+javaName+"\") = \""+mime+"\"");399} else {400throw new Error("Bad preferred mime type for: '"+javaName+"': expected '"+401expected+"' but got '"+mime+"'");402}403}404405private static Collection<String> getValues(String val) {406int pos = val.indexOf(' ');407if (pos < 0) {408return Collections.singletonList(val);409}410//lastPrintable =411// Integer.decode(val.substring(pos).trim()).intValue();412StringTokenizer st =413new StringTokenizer(val.substring(0, pos), ",");414final List<String> values = new ArrayList<>(st.countTokens());415while (st.hasMoreTokens()) {416values.add(st.nextToken());417}418return values;419}420421// can be called in main() to help debugging.422// Prints out all available charsets and their recognized aliases423// as returned by the Charset API.424private static void printAllCharsets() {425Map<String, Charset> all = Charset.availableCharsets();426System.out.println("\n=========================================\n");427for (String can : all.keySet()) {428System.out.println(can + ": " + all.get(can).aliases());429}430}431}432433434