8054834: Modular Source Code
Reviewed-by: alanb, chegar, ihse, mduigou
Contributed-by: alan.bateman@oracle.com, alex.buckley@oracle.com, chris.hegarty@oracle.com, erik.joelsson@oracle.com, jonathan.gibbons@oracle.com, karen.kinnear@oracle.com, magnus.ihse.bursie@oracle.com, mandy.chung@oracle.com, mark.reinhold@oracle.com, paul.sandoz@oracle.com
/*
* Copyright (c) 1999, 2014, 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 sun.applet;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;
import sun.net.www.ParseUtil;
/**
* The main entry point into AppletViewer.
*/
public class Main {
/**
* The file which contains all of the AppletViewer specific properties.
*/
static File theUserPropertiesFile;
/**
* The default key/value pairs for the required user-specific properties.
*/
static final String [][] avDefaultUserProps = {
// There's a bootstrapping problem here. If we don't have a proxyHost,
// then we will not be able to connect to a URL outside the firewall;
// however, there's no way for us to set the proxyHost without starting
// AppletViewer. This problem existed before the re-write.
{"http.proxyHost", ""},
{"http.proxyPort", "80"},
{"package.restrict.access.sun", "true"}
};
static {
File userHome = new File(System.getProperty("user.home"));
// make sure we can write to this location
userHome.canWrite();
theUserPropertiesFile = new File(userHome, ".appletviewer");
}
// i18n
private static AppletMessageHandler amh = new AppletMessageHandler("appletviewer");
/**
* Member variables set according to options passed in to AppletViewer.
*/
private boolean debugFlag = false;
private boolean helpFlag = false;
private String encoding = null;
private boolean noSecurityFlag = false;
private static boolean cmdLineTestFlag = false;
/**
* The list of valid URLs passed in to AppletViewer.
*/
private static Vector<URL> urlList = new Vector<>(1);
// This is used in init(). Getting rid of this is desirable but depends
// on whether the property that uses it is necessary/standard.
public static final String theVersion = System.getProperty("java.version");
/**
* The main entry point into AppletViewer.
*/
public static void main(String [] args) {
Main m = new Main();
int ret = m.run(args);
// Exit immediately if we got some sort of error along the way.
// For debugging purposes, if we have passed in "-XcmdLineTest" we
// force a premature exit.
if ((ret != 0) || (cmdLineTestFlag))
System.exit(ret);
}
private int run(String [] args) {
// DECODE ARGS
try {
if (args.length == 0) {
usage();
return 0;
}
for (int i = 0; i < args.length; ) {
int j = decodeArg(args, i);
if (j == 0) {
throw new ParseException(lookup("main.err.unrecognizedarg",
args[i]));
}
i += j;
}
} catch (ParseException e) {
System.err.println(e.getMessage());
return 1;
}
// CHECK ARGUMENTS
if (helpFlag) {
usage();
return 0;
}
if (urlList.size() == 0) {
System.err.println(lookup("main.err.inputfile"));
return 1;
}
if (debugFlag) {
// START A DEBUG SESSION
// Given the current architecture, we will end up decoding the
// arguments again, but at least we are guaranteed to have
// arguments which are valid.
return invokeDebugger(args);
}
// INSTALL THE SECURITY MANAGER (if necessary)
if (!noSecurityFlag && (System.getSecurityManager() == null))
init();
// LAUNCH APPLETVIEWER FOR EACH URL
for (int i = 0; i < urlList.size(); i++) {
try {
// XXX 5/17 this parsing method should be changed/fixed so that
// it doesn't do both parsing of the html file and launching of
// the AppletPanel
AppletViewer.parse(urlList.elementAt(i), encoding);
} catch (IOException e) {
System.err.println(lookup("main.err.io", e.getMessage()));
return 1;
}
}
return 0;
}
private static void usage() {
System.out.println(lookup("usage"));
}
/**
* Decode a single argument in an array and return the number of elements
* used.
*
* @param args The array of arguments.
* @param i The argument to decode.
* @return The number of array elements used when the argument was
* decoded.
* @exception ParseException
* Thrown when there is a problem with something in the
* argument array.
*/
private int decodeArg(String [] args, int i) throws ParseException {
String arg = args[i];
int argc = args.length;
if ("-help".equalsIgnoreCase(arg) || "-?".equals(arg)) {
helpFlag = true;
return 1;
} else if ("-encoding".equals(arg) && (i < argc - 1)) {
if (encoding != null)
throw new ParseException(lookup("main.err.dupoption", arg));
encoding = args[++i];
return 2;
} else if ("-debug".equals(arg)) {
debugFlag = true;
return 1;
} else if ("-Xnosecurity".equals(arg)) {
// This is an undocumented (and, in the future, unsupported)
// flag which prevents AppletViewer from installing its own
// SecurityManager.
System.err.println();
System.err.println(lookup("main.warn.nosecmgr"));
System.err.println();
noSecurityFlag = true;
return 1;
} else if ("-XcmdLineTest".equals(arg)) {
// This is an internal flag which should be used for command-line
// testing. It instructs AppletViewer to force a premature exit
// immediately after the applet has been launched.
cmdLineTestFlag = true;
return 1;
} else if (arg.startsWith("-")) {
throw new ParseException(lookup("main.err.unsupportedopt", arg));
} else {
// we found what we hope is a url
URL url = parseURL(arg);
if (url != null) {
urlList.addElement(url);
return 1;
}
}
return 0;
}
/**
* Following the relevant RFC, construct a valid URL based on the passed in
* string.
*
* @param url a string which represents either a relative or absolute URL.
* @return a URL when the passed in string can be interpreted according
* to the RFC, <code>null</code> otherwise.
* @exception ParseException
* Thrown when we are unable to construct a proper URL from the
* passed in string.
*/
private URL parseURL(String url) throws ParseException {
URL u = null;
// prefix of the urls with 'file' scheme
String prefix = "file:";
try {
if (url.indexOf(':') <= 1)
{
// appletviewer accepts only unencoded filesystem paths
u = ParseUtil.fileToEncodedURL(new File(url));
} else if (url.startsWith(prefix) &&
url.length() != prefix.length() &&
!(new File(url.substring(prefix.length())).isAbsolute()))
{
// relative file URL, like this "file:index.html"
// ensure that this file URL is absolute
// ParseUtil.fileToEncodedURL should be done last (see 6329251)
String path = ParseUtil.fileToEncodedURL(new File(System.getProperty("user.dir"))).getPath() +
url.substring(prefix.length());
u = new URL("file", "", path);
} else {
// appletviewer accepts only encoded urls
u = new URL(url);
}
} catch (MalformedURLException e) {
throw new ParseException(lookup("main.err.badurl",
url, e.getMessage()));
}
return u;
}
/**
* Invoke the debugger with the arguments passed in to appletviewer.
*
* @param args The arguments passed into the debugger.
* @return <code>0</code> if the debugger is invoked successfully,
* <code>1</code> otherwise.
*/
private int invokeDebugger(String [] args) {
// CONSTRUCT THE COMMAND LINE
String [] newArgs = new String[args.length + 1];
int current = 0;
// Add a -classpath argument that prevents
// the debugger from launching appletviewer with the default of
// ".". appletviewer's classpath should never contain valid
// classes since they will result in security exceptions.
// Ideally, the classpath should be set to "", but the VM won't
// allow an empty classpath, so a phony directory name is used.
String phonyDir = System.getProperty("java.home") +
File.separator + "phony";
newArgs[current++] = "-Djava.class.path=" + phonyDir;
// Appletviewer's main class is the debuggee
newArgs[current++] = "sun.applet.Main";
// Append all the of the original appletviewer arguments,
// leaving out the "-debug" option.
for (int i = 0; i < args.length; i++) {
if (!("-debug".equals(args[i]))) {
newArgs[current++] = args[i];
}
}
// LAUNCH THE DEBUGGER
// Reflection is used for two reasons:
// 1) The debugger classes are on classpath and thus must be loaded
// by the application class loader. (Currently, appletviewer are
// loaded through the boot class path out of rt.jar.)
// 2) Reflection removes any build dependency between appletviewer
// and jdb.
try {
Class<?> c = Class.forName("com.sun.tools.example.debug.tty.TTY", true,
ClassLoader.getSystemClassLoader());
Method m = c.getDeclaredMethod("main",
new Class<?>[] { String[].class });
m.invoke(null, new Object[] { newArgs });
} catch (ClassNotFoundException cnfe) {
System.err.println(lookup("main.debug.cantfinddebug"));
return 1;
} catch (NoSuchMethodException nsme) {
System.err.println(lookup("main.debug.cantfindmain"));
return 1;
} catch (InvocationTargetException ite) {
System.err.println(lookup("main.debug.exceptionindebug"));
return 1;
} catch (IllegalAccessException iae) {
System.err.println(lookup("main.debug.cantaccess"));
return 1;
}
return 0;
}
private void init() {
// GET APPLETVIEWER USER-SPECIFIC PROPERTIES
Properties avProps = getAVProps();
// ADD OTHER RANDOM PROPERTIES
// XXX 5/18 need to revisit why these are here, is there some
// standard for what is available?
// Standard browser properties
avProps.put("browser", "sun.applet.AppletViewer");
avProps.put("browser.version", "1.06");
avProps.put("browser.vendor", "Oracle Corporation");
avProps.put("http.agent", "Java(tm) 2 SDK, Standard Edition v" + theVersion);
// Define which packages can be extended by applets
// XXX 5/19 probably not needed, not checked in AppletSecurity
avProps.put("package.restrict.definition.java", "true");
avProps.put("package.restrict.definition.sun", "true");
// Define which properties can be read by applets.
// A property named by "key" can be read only when its twin
// property "key.applet" is true. The following ten properties
// are open by default. Any other property can be explicitly
// opened up by the browser user by calling appletviewer with
// -J-Dkey.applet=true
avProps.put("java.version.applet", "true");
avProps.put("java.vendor.applet", "true");
avProps.put("java.vendor.url.applet", "true");
avProps.put("java.class.version.applet", "true");
avProps.put("os.name.applet", "true");
avProps.put("os.version.applet", "true");
avProps.put("os.arch.applet", "true");
avProps.put("file.separator.applet", "true");
avProps.put("path.separator.applet", "true");
avProps.put("line.separator.applet", "true");
// Read in the System properties. If something is going to be
// over-written, warn about it.
Properties sysProps = System.getProperties();
for (Enumeration<?> e = sysProps.propertyNames(); e.hasMoreElements(); ) {
String key = (String) e.nextElement();
String val = sysProps.getProperty(key);
String oldVal;
if ((oldVal = (String) avProps.setProperty(key, val)) != null)
System.err.println(lookup("main.warn.prop.overwrite", key,
oldVal, val));
}
// INSTALL THE PROPERTY LIST
System.setProperties(avProps);
// Create and install the security manager
if (!noSecurityFlag) {
System.setSecurityManager(new AppletSecurity());
} else {
System.err.println(lookup("main.nosecmgr"));
}
// REMIND: Create and install a socket factory!
}
/**
* Read the AppletViewer user-specific properties. Typically, these
* properties should reside in the file $USER/.appletviewer. If this file
* does not exist, one will be created. Information for this file will
* be gleaned from $USER/.hotjava/properties. If that file does not exist,
* then default values will be used.
*
* @return A Properties object containing all of the AppletViewer
* user-specific properties.
*/
private Properties getAVProps() {
Properties avProps = new Properties();
File dotAV = theUserPropertiesFile;
if (dotAV.exists()) {
// we must have already done the conversion
if (dotAV.canRead()) {
// just read the file
avProps = getAVProps(dotAV);
} else {
// send out warning and use defaults
System.err.println(lookup("main.warn.cantreadprops",
dotAV.toString()));
avProps = setDefaultAVProps();
}
} else {
// create the $USER/.appletviewer file
// see if $USER/.hotjava/properties exists
File userHome = new File(System.getProperty("user.home"));
File dotHJ = new File(userHome, ".hotjava");
dotHJ = new File(dotHJ, "properties");
if (dotHJ.exists()) {
// just read the file
avProps = getAVProps(dotHJ);
} else {
// send out warning and use defaults
System.err.println(lookup("main.warn.cantreadprops",
dotHJ.toString()));
avProps = setDefaultAVProps();
}
// SAVE THE FILE
try (FileOutputStream out = new FileOutputStream(dotAV)) {
avProps.store(out, lookup("main.prop.store"));
} catch (IOException e) {
System.err.println(lookup("main.err.prop.cantsave",
dotAV.toString()));
}
}
return avProps;
}
/**
* Set the AppletViewer user-specific properties to be the default values.
*
* @return A Properties object containing all of the AppletViewer
* user-specific properties, set to the default values.
*/
private Properties setDefaultAVProps() {
Properties avProps = new Properties();
for (int i = 0; i < avDefaultUserProps.length; i++) {
avProps.setProperty(avDefaultUserProps[i][0],
avDefaultUserProps[i][1]);
}
return avProps;
}
/**
* Given a file, find only the properties that are setable by AppletViewer.
*
* @param inFile A Properties file from which we select the properties of
* interest.
* @return A Properties object containing all of the AppletViewer
* user-specific properties.
*/
private Properties getAVProps(File inFile) {
Properties avProps = new Properties();
// read the file
Properties tmpProps = new Properties();
try (FileInputStream in = new FileInputStream(inFile)) {
tmpProps.load(new BufferedInputStream(in));
} catch (IOException e) {
System.err.println(lookup("main.err.prop.cantread", inFile.toString()));
}
// pick off the properties we care about
for (int i = 0; i < avDefaultUserProps.length; i++) {
String value = tmpProps.getProperty(avDefaultUserProps[i][0]);
if (value != null) {
// the property exists in the file, so replace the default
avProps.setProperty(avDefaultUserProps[i][0], value);
} else {
// just use the default
avProps.setProperty(avDefaultUserProps[i][0],
avDefaultUserProps[i][1]);
}
}
return avProps;
}
/**
* Methods for easier i18n handling.
*/
private static String lookup(String key) {
return amh.getMessage(key);
}
private static String lookup(String key, String arg0) {
return amh.getMessage(key, arg0);
}
private static String lookup(String key, String arg0, String arg1) {
return amh.getMessage(key, arg0, arg1);
}
private static String lookup(String key, String arg0, String arg1,
String arg2) {
return amh.getMessage(key, arg0, arg1, arg2);
}
@SuppressWarnings("serial") // JDK implementation class
class ParseException extends RuntimeException
{
public ParseException(String msg) {
super(msg);
}
public ParseException(Throwable t) {
super(t.getMessage());
this.t = t;
}
Throwable t = null;
}
}