|
Java example source code file (JavapTask.java)
The JavapTask.java Java example source code/* * Copyright (c) 2007, 2013, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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. */ package com.sun.tools.javap; import java.io.EOFException; import java.io.FileNotFoundException; import java.io.FilterInputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.net.URI; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import javax.lang.model.element.Modifier; import javax.lang.model.element.NestingKind; import javax.tools.Diagnostic; import javax.tools.DiagnosticListener; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.StandardLocation; import com.sun.tools.classfile.*; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; /** * "Main" class for javap, normally accessed from the command line * via Main, or from JSR199 via DisassemblerTool. * * <p>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> */ public class JavapTask implements DisassemblerTool.DisassemblerTask, Messages { public class BadArgs extends Exception { static final long serialVersionUID = 8765093759964640721L; BadArgs(String key, Object... args) { super(JavapTask.this.getMessage(key, args)); this.key = key; this.args = args; } BadArgs showUsage(boolean b) { showUsage = b; return this; } final String key; final Object[] args; boolean showUsage; } static abstract class Option { Option(boolean hasArg, String... aliases) { this.hasArg = hasArg; this.aliases = aliases; } boolean matches(String opt) { for (String a: aliases) { if (a.equals(opt)) return true; } return false; } boolean ignoreRest() { return false; } abstract void process(JavapTask task, String opt, String arg) throws BadArgs; final boolean hasArg; final String[] aliases; } static final Option[] recognizedOptions = { new Option(false, "-help", "--help", "-?") { void process(JavapTask task, String opt, String arg) { task.options.help = true; } }, new Option(false, "-version") { void process(JavapTask task, String opt, String arg) { task.options.version = true; } }, new Option(false, "-fullversion") { void process(JavapTask task, String opt, String arg) { task.options.fullVersion = true; } }, new Option(false, "-v", "-verbose", "-all") { void process(JavapTask task, String opt, String arg) { task.options.verbose = true; task.options.showDescriptors = true; task.options.showFlags = true; task.options.showAllAttrs = true; } }, new Option(false, "-l") { void process(JavapTask task, String opt, String arg) { task.options.showLineAndLocalVariableTables = true; } }, new Option(false, "-public") { void process(JavapTask task, String opt, String arg) { task.options.accessOptions.add(opt); task.options.showAccess = AccessFlags.ACC_PUBLIC; } }, new Option(false, "-protected") { void process(JavapTask task, String opt, String arg) { task.options.accessOptions.add(opt); task.options.showAccess = AccessFlags.ACC_PROTECTED; } }, new Option(false, "-package") { void process(JavapTask task, String opt, String arg) { task.options.accessOptions.add(opt); task.options.showAccess = 0; } }, new Option(false, "-p", "-private") { void process(JavapTask task, String opt, String arg) { if (!task.options.accessOptions.contains("-p") && !task.options.accessOptions.contains("-private")) { task.options.accessOptions.add(opt); } task.options.showAccess = AccessFlags.ACC_PRIVATE; } }, new Option(false, "-c") { void process(JavapTask task, String opt, String arg) { task.options.showDisassembled = true; } }, new Option(false, "-s") { void process(JavapTask task, String opt, String arg) { task.options.showDescriptors = true; } }, // new Option(false, "-all") { // void process(JavapTask task, String opt, String arg) { // task.options.showAllAttrs = true; // } // }, new Option(false, "-h") { void process(JavapTask task, String opt, String arg) throws BadArgs { throw task.new BadArgs("err.h.not.supported"); } }, new Option(false, "-verify", "-verify-verbose") { void process(JavapTask task, String opt, String arg) throws BadArgs { throw task.new BadArgs("err.verify.not.supported"); } }, new Option(false, "-sysinfo") { void process(JavapTask task, String opt, String arg) { task.options.sysInfo = true; } }, new Option(false, "-Xold") { void process(JavapTask task, String opt, String arg) throws BadArgs { task.log.println(task.getMessage("warn.Xold.not.supported")); } }, new Option(false, "-Xnew") { void process(JavapTask task, String opt, String arg) throws BadArgs { // ignore: this _is_ the new version } }, new Option(false, "-XDcompat") { void process(JavapTask task, String opt, String arg) { task.options.compat = true; } }, new Option(false, "-XDdetails") { void process(JavapTask task, String opt, String arg) { task.options.details = EnumSet.allOf(InstructionDetailWriter.Kind.class); } }, new Option(false, "-XDdetails:") { @Override boolean matches(String opt) { int sep = opt.indexOf(":"); return sep != -1 && super.matches(opt.substring(0, sep + 1)); } void process(JavapTask task, String opt, String arg) throws BadArgs { int sep = opt.indexOf(":"); for (String v: opt.substring(sep + 1).split("[,: ]+")) { if (!handleArg(task, v)) throw task.new BadArgs("err.invalid.arg.for.option", v); } } boolean handleArg(JavapTask task, String arg) { if (arg.length() == 0) return true; if (arg.equals("all")) { task.options.details = EnumSet.allOf(InstructionDetailWriter.Kind.class); return true; } boolean on = true; if (arg.startsWith("-")) { on = false; arg = arg.substring(1); } for (InstructionDetailWriter.Kind k: InstructionDetailWriter.Kind.values()) { if (arg.equalsIgnoreCase(k.option)) { if (on) task.options.details.add(k); else task.options.details.remove(k); return true; } } return false; } }, new Option(false, "-constants") { void process(JavapTask task, String opt, String arg) { task.options.showConstants = true; } }, new Option(false, "-XDinner") { void process(JavapTask task, String opt, String arg) { task.options.showInnerClasses = true; } }, new Option(false, "-XDindent:") { @Override boolean matches(String opt) { int sep = opt.indexOf(":"); return sep != -1 && super.matches(opt.substring(0, sep + 1)); } void process(JavapTask task, String opt, String arg) throws BadArgs { int sep = opt.indexOf(":"); try { task.options.indentWidth = Integer.valueOf(opt.substring(sep + 1)); } catch (NumberFormatException e) { } } }, new Option(false, "-XDtab:") { @Override boolean matches(String opt) { int sep = opt.indexOf(":"); return sep != -1 && super.matches(opt.substring(0, sep + 1)); } void process(JavapTask task, String opt, String arg) throws BadArgs { int sep = opt.indexOf(":"); try { task.options.tabColumn = Integer.valueOf(opt.substring(sep + 1)); } catch (NumberFormatException e) { } } } }; public JavapTask() { context = new Context(); context.put(Messages.class, this); options = Options.instance(context); attributeFactory = new Attribute.Factory(); } public JavapTask(Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener) { this(); this.log = getPrintWriterForWriter(out); this.fileManager = fileManager; this.diagnosticListener = diagnosticListener; } public JavapTask(Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classes) { this(out, fileManager, diagnosticListener); this.classes = new ArrayList<String>(); for (String classname: classes) { classname.getClass(); // null-check this.classes.add(classname); } try { if (options != null) handleOptions(options, false); } catch (BadArgs e) { throw new IllegalArgumentException(e.getMessage()); } } public void setLocale(Locale locale) { if (locale == null) locale = Locale.getDefault(); task_locale = locale; } public void setLog(Writer log) { this.log = getPrintWriterForWriter(log); } public void setLog(OutputStream s) { setLog(getPrintWriterForStream(s)); } private static PrintWriter getPrintWriterForStream(OutputStream s) { return new PrintWriter(s == null ? System.err : s, true); } private static PrintWriter getPrintWriterForWriter(Writer w) { if (w == null) return getPrintWriterForStream(null); else if (w instanceof PrintWriter) return (PrintWriter) w; else return new PrintWriter(w, true); } public void setDiagnosticListener(DiagnosticListener<? super JavaFileObject> dl) { diagnosticListener = dl; } public void setDiagnosticListener(OutputStream s) { setDiagnosticListener(getDiagnosticListenerForStream(s)); } private DiagnosticListener<JavaFileObject> getDiagnosticListenerForStream(OutputStream s) { return getDiagnosticListenerForWriter(getPrintWriterForStream(s)); } private DiagnosticListener<JavaFileObject> getDiagnosticListenerForWriter(Writer w) { final PrintWriter pw = getPrintWriterForWriter(w); return new DiagnosticListener<JavaFileObject> () { public void report(Diagnostic<? extends JavaFileObject> diagnostic) { switch (diagnostic.getKind()) { case ERROR: pw.print(getMessage("err.prefix")); break; case WARNING: pw.print(getMessage("warn.prefix")); break; case NOTE: pw.print(getMessage("note.prefix")); break; } pw.print(" "); pw.println(diagnostic.getMessage(null)); } }; } /** Result codes. */ static final int EXIT_OK = 0, // Compilation completed with no errors. EXIT_ERROR = 1, // Completed but reported errors. EXIT_CMDERR = 2, // Bad command-line arguments EXIT_SYSERR = 3, // System error or resource exhaustion. EXIT_ABNORMAL = 4; // Compiler terminated abnormally int run(String[] args) { try { handleOptions(args); // the following gives consistent behavior with javac if (classes == null || classes.size() == 0) { if (options.help || options.version || options.fullVersion) return EXIT_OK; else return EXIT_CMDERR; } try { return run(); } finally { if (defaultFileManager != null) { try { defaultFileManager.close(); defaultFileManager = null; } catch (IOException e) { throw new InternalError(e); } } } } catch (BadArgs e) { reportError(e.key, e.args); if (e.showUsage) { log.println(getMessage("main.usage.summary", progname)); } return EXIT_CMDERR; } catch (InternalError e) { Object[] e_args; if (e.getCause() == null) e_args = e.args; else { e_args = new Object[e.args.length + 1]; e_args[0] = e.getCause(); System.arraycopy(e.args, 0, e_args, 1, e.args.length); } reportError("err.internal.error", e_args); return EXIT_ABNORMAL; } finally { log.flush(); } } public void handleOptions(String[] args) throws BadArgs { handleOptions(Arrays.asList(args), true); } private void handleOptions(Iterable<String> args, boolean allowClasses) throws BadArgs { if (log == null) { log = getPrintWriterForStream(System.out); if (diagnosticListener == null) diagnosticListener = getDiagnosticListenerForStream(System.err); } else { if (diagnosticListener == null) diagnosticListener = getDiagnosticListenerForWriter(log); } if (fileManager == null) fileManager = getDefaultFileManager(diagnosticListener, log); Iterator<String> iter = args.iterator(); boolean noArgs = !iter.hasNext(); while (iter.hasNext()) { String arg = iter.next(); if (arg.startsWith("-")) handleOption(arg, iter); else if (allowClasses) { if (classes == null) classes = new ArrayList<String>(); classes.add(arg); while (iter.hasNext()) classes.add(iter.next()); } else throw new BadArgs("err.unknown.option", arg).showUsage(true); } if (!options.compat && options.accessOptions.size() > 1) { StringBuilder sb = new StringBuilder(); for (String opt: options.accessOptions) { if (sb.length() > 0) sb.append(" "); sb.append(opt); } throw new BadArgs("err.incompatible.options", sb); } if ((classes == null || classes.size() == 0) && !(noArgs || options.help || options.version || options.fullVersion)) { throw new BadArgs("err.no.classes.specified"); } if (noArgs || options.help) showHelp(); if (options.version || options.fullVersion) showVersion(options.fullVersion); } private void handleOption(String name, Iterator<String> rest) throws BadArgs { for (Option o: recognizedOptions) { if (o.matches(name)) { if (o.hasArg) { if (rest.hasNext()) o.process(this, name, rest.next()); else throw new BadArgs("err.missing.arg", name).showUsage(true); } else o.process(this, name, null); if (o.ignoreRest()) { while (rest.hasNext()) rest.next(); } return; } } if (fileManager.handleOption(name, rest)) return; throw new BadArgs("err.unknown.option", name).showUsage(true); } public Boolean call() { return run() == 0; } public int run() { if (classes == null || classes.isEmpty()) { return EXIT_ERROR; } context.put(PrintWriter.class, log); ClassWriter classWriter = ClassWriter.instance(context); SourceWriter sourceWriter = SourceWriter.instance(context); sourceWriter.setFileManager(fileManager); attributeFactory.setCompat(options.compat); int result = EXIT_OK; for (String className: classes) { try { result = writeClass(classWriter, className); } catch (ConstantPoolException e) { reportError("err.bad.constant.pool", className, e.getLocalizedMessage()); result = EXIT_ERROR; } catch (EOFException e) { reportError("err.end.of.file", className); result = EXIT_ERROR; } catch (FileNotFoundException e) { reportError("err.file.not.found", e.getLocalizedMessage()); result = EXIT_ERROR; } catch (IOException e) { //e.printStackTrace(); Object msg = e.getLocalizedMessage(); if (msg == null) { msg = e; } reportError("err.ioerror", className, msg); result = EXIT_ERROR; } catch (Throwable t) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); pw.close(); reportError("err.crash", t.toString(), sw.toString()); result = EXIT_ABNORMAL; } } return result; } protected int writeClass(ClassWriter classWriter, String className) throws IOException, ConstantPoolException { JavaFileObject fo = open(className); if (fo == null) { reportError("err.class.not.found", className); return EXIT_ERROR; } ClassFileInfo cfInfo = read(fo); if (!className.endsWith(".class")) { String cfName = cfInfo.cf.getName(); if (!cfName.replaceAll("[/$]", ".").equals(className.replaceAll("[/$]", "."))) { reportWarning("warn.unexpected.class", className, cfName.replace('/', '.')); } } write(cfInfo); if (options.showInnerClasses) { ClassFile cf = cfInfo.cf; Attribute a = cf.getAttribute(Attribute.InnerClasses); if (a instanceof InnerClasses_attribute) { InnerClasses_attribute inners = (InnerClasses_attribute) a; try { int result = EXIT_OK; for (int i = 0; i < inners.classes.length; i++) { int outerIndex = inners.classes[i].outer_class_info_index; ConstantPool.CONSTANT_Class_info outerClassInfo = cf.constant_pool.getClassInfo(outerIndex); String outerClassName = outerClassInfo.getName(); if (outerClassName.equals(cf.getName())) { int innerIndex = inners.classes[i].inner_class_info_index; ConstantPool.CONSTANT_Class_info innerClassInfo = cf.constant_pool.getClassInfo(innerIndex); String innerClassName = innerClassInfo.getName(); classWriter.println("// inner class " + innerClassName.replaceAll("[/$]", ".")); classWriter.println(); result = writeClass(classWriter, innerClassName); if (result != EXIT_OK) return result; } } return result; } catch (ConstantPoolException e) { reportError("err.bad.innerclasses.attribute", className); return EXIT_ERROR; } } else if (a != null) { reportError("err.bad.innerclasses.attribute", className); return EXIT_ERROR; } } return EXIT_OK; } protected JavaFileObject open(String className) throws IOException { // for compatibility, first see if it is a class name JavaFileObject fo = getClassFileObject(className); if (fo != null) return fo; // see if it is an inner class, by replacing dots to $, starting from the right String cn = className; int lastDot; while ((lastDot = cn.lastIndexOf(".")) != -1) { cn = cn.substring(0, lastDot) + "$" + cn.substring(lastDot + 1); fo = getClassFileObject(cn); if (fo != null) return fo; } if (!className.endsWith(".class")) return null; if (fileManager instanceof StandardJavaFileManager) { StandardJavaFileManager sfm = (StandardJavaFileManager) fileManager; fo = sfm.getJavaFileObjects(className).iterator().next(); if (fo != null && fo.getLastModified() != 0) { return fo; } } // see if it is a URL, and if so, wrap it in just enough of a JavaFileObject // to suit javap's needs if (className.matches("^[A-Za-z]+:.*")) { try { final URI uri = new URI(className); final URL url = uri.toURL(); final URLConnection conn = url.openConnection(); return new JavaFileObject() { public Kind getKind() { return JavaFileObject.Kind.CLASS; } public boolean isNameCompatible(String simpleName, Kind kind) { throw new UnsupportedOperationException(); } public NestingKind getNestingKind() { throw new UnsupportedOperationException(); } public Modifier getAccessLevel() { throw new UnsupportedOperationException(); } public URI toUri() { return uri; } public String getName() { return url.toString(); } public InputStream openInputStream() throws IOException { return conn.getInputStream(); } public OutputStream openOutputStream() throws IOException { throw new UnsupportedOperationException(); } public Reader openReader(boolean ignoreEncodingErrors) throws IOException { throw new UnsupportedOperationException(); } public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { throw new UnsupportedOperationException(); } public Writer openWriter() throws IOException { throw new UnsupportedOperationException(); } public long getLastModified() { return conn.getLastModified(); } public boolean delete() { throw new UnsupportedOperationException(); } }; } catch (URISyntaxException ignore) { } catch (IOException ignore) { } } return null; } public static class ClassFileInfo { ClassFileInfo(JavaFileObject fo, ClassFile cf, byte[] digest, int size) { this.fo = fo; this.cf = cf; this.digest = digest; this.size = size; } public final JavaFileObject fo; public final ClassFile cf; public final byte[] digest; public final int size; } public ClassFileInfo read(JavaFileObject fo) throws IOException, ConstantPoolException { InputStream in = fo.openInputStream(); try { SizeInputStream sizeIn = null; MessageDigest md = null; if (options.sysInfo || options.verbose) { try { md = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException ignore) { } in = new DigestInputStream(in, md); in = sizeIn = new SizeInputStream(in); } ClassFile cf = ClassFile.read(in, attributeFactory); byte[] digest = (md == null) ? null : md.digest(); int size = (sizeIn == null) ? -1 : sizeIn.size(); return new ClassFileInfo(fo, cf, digest, size); } finally { in.close(); } } public void write(ClassFileInfo info) { ClassWriter classWriter = ClassWriter.instance(context); if (options.sysInfo || options.verbose) { classWriter.setFile(info.fo.toUri()); classWriter.setLastModified(info.fo.getLastModified()); classWriter.setDigest("MD5", info.digest); classWriter.setFileSize(info.size); } classWriter.write(info.cf); } protected void setClassFile(ClassFile classFile) { ClassWriter classWriter = ClassWriter.instance(context); classWriter.setClassFile(classFile); } protected void setMethod(Method enclosingMethod) { ClassWriter classWriter = ClassWriter.instance(context); classWriter.setMethod(enclosingMethod); } protected void write(Attribute value) { AttributeWriter attrWriter = AttributeWriter.instance(context); ClassWriter classWriter = ClassWriter.instance(context); ClassFile cf = classWriter.getClassFile(); attrWriter.write(cf, value, cf.constant_pool); } protected void write(Attributes attrs) { AttributeWriter attrWriter = AttributeWriter.instance(context); ClassWriter classWriter = ClassWriter.instance(context); ClassFile cf = classWriter.getClassFile(); attrWriter.write(cf, attrs, cf.constant_pool); } protected void write(ConstantPool constant_pool) { ConstantWriter constantWriter = ConstantWriter.instance(context); constantWriter.writeConstantPool(constant_pool); } protected void write(ConstantPool constant_pool, int value) { ConstantWriter constantWriter = ConstantWriter.instance(context); constantWriter.write(value); } protected void write(ConstantPool.CPInfo value) { ConstantWriter constantWriter = ConstantWriter.instance(context); constantWriter.println(value); } protected void write(Field value) { ClassWriter classWriter = ClassWriter.instance(context); classWriter.writeField(value); } protected void write(Method value) { ClassWriter classWriter = ClassWriter.instance(context); classWriter.writeMethod(value); } private JavaFileManager getDefaultFileManager(final DiagnosticListener<? super JavaFileObject> dl, PrintWriter log) { if (defaultFileManager == null) defaultFileManager = JavapFileManager.create(dl, log); return defaultFileManager; } private JavaFileObject getClassFileObject(String className) throws IOException { JavaFileObject fo; fo = fileManager.getJavaFileForInput(StandardLocation.PLATFORM_CLASS_PATH, className, JavaFileObject.Kind.CLASS); if (fo == null) fo = fileManager.getJavaFileForInput(StandardLocation.CLASS_PATH, className, JavaFileObject.Kind.CLASS); return fo; } private void showHelp() { log.println(getMessage("main.usage", progname)); for (Option o: recognizedOptions) { String name = o.aliases[0].substring(1); // there must always be at least one name if (name.startsWith("X") || name.equals("fullversion") || name.equals("h") || name.equals("verify")) continue; log.println(getMessage("main.opt." + name)); } String[] fmOptions = { "-classpath", "-cp", "-bootclasspath" }; for (String o: fmOptions) { if (fileManager.isSupportedOption(o) == -1) continue; String name = o.substring(1); log.println(getMessage("main.opt." + name)); } } private void showVersion(boolean full) { log.println(version(full ? "full" : "release")); } private static final String versionRBName = "com.sun.tools.javap.resources.version"; private static ResourceBundle versionRB; private String version(String key) { // key=version: mm.nn.oo[-milestone] // key=full: mm.mm.oo[-milestone]-build if (versionRB == null) { try { versionRB = ResourceBundle.getBundle(versionRBName); } catch (MissingResourceException e) { return getMessage("version.resource.missing", System.getProperty("java.version")); } } try { return versionRB.getString(key); } catch (MissingResourceException e) { return getMessage("version.unknown", System.getProperty("java.version")); } } private void reportError(String key, Object... args) { diagnosticListener.report(createDiagnostic(Diagnostic.Kind.ERROR, key, args)); } private void reportNote(String key, Object... args) { diagnosticListener.report(createDiagnostic(Diagnostic.Kind.NOTE, key, args)); } private void reportWarning(String key, Object... args) { diagnosticListener.report(createDiagnostic(Diagnostic.Kind.WARNING, key, args)); } private Diagnostic<JavaFileObject> createDiagnostic( final Diagnostic.Kind kind, final String key, final Object... args) { return new Diagnostic<JavaFileObject>() { public Kind getKind() { return kind; } public JavaFileObject getSource() { return null; } public long getPosition() { return Diagnostic.NOPOS; } public long getStartPosition() { return Diagnostic.NOPOS; } public long getEndPosition() { return Diagnostic.NOPOS; } public long getLineNumber() { return Diagnostic.NOPOS; } public long getColumnNumber() { return Diagnostic.NOPOS; } public String getCode() { return key; } public String getMessage(Locale locale) { return JavapTask.this.getMessage(locale, key, args); } @Override public String toString() { return getClass().getName() + "[key=" + key + ",args=" + Arrays.asList(args) + "]"; } }; } public String getMessage(String key, Object... args) { return getMessage(task_locale, key, args); } public String getMessage(Locale locale, String key, Object... args) { if (bundles == null) { // could make this a HashMap<Locale,SoftReference Other Java examples (source code examples)Here is a short list of links related to this Java JavapTask.java source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.