package com.intellij.flex;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
public class FcshLauncher {
// For each element XXXX of this array class com.intellij.flex.Fcsh4WithFixXXXX must exist
private static final int[] FLEX_4_SDK_BUILDS_THAT_HAVE_FIX = new int[]{0};
// For each element XXXX of this array class com.intellij.flex.Fcsh45WithFixXXXX must exist
private static final int[] FLEX_45_SDK_BUILDS_THAT_HAVE_FIX = new int[]{0};
public static void main(final String[] args) {
try {
final String flexSdkVersion = getFlexSdkVersion();
if (flexSdkVersion == null) {
launchFcshWithoutFix(args);
} else {
launchFcshWithFix(flexSdkVersion, args);
}
} catch (OutOfMemoryError oomError) {
System.out.println("(fcsh) out of memory");
System.exit(0);
}
}
private static void launchFcshWithoutFix(final String[] args) {
Class entryPointClass;
entryPointClass = findClass("flex2.tools.Fcsh");
if (entryPointClass == null) {
entryPointClass = findClass("flex2.tools.SimpleShell");
}
if (entryPointClass != null) {
try {
final Method main = entryPointClass.getMethod("main", args.getClass());
main.invoke(null, new Object[]{args});
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
if (e.getCause() instanceof VirtualMachineError) {
throw (VirtualMachineError) e.getCause();
} else {
e.printStackTrace();
}
}
} else {
System.err.println("Flex SDK is corrupted.");
}
}
private static void launchFcshWithFix(final String flexSdkVersion, final String[] args) {
final Class entryPointClass = getEntryPointClass(flexSdkVersion);
if (entryPointClass == null) {
launchFcshWithoutFix(args);
} else {
try {
final Method main = entryPointClass.getMethod("main", args.getClass());
try {
main.invoke(null, new Object[]{args});
} catch (InvocationTargetException e) {
if (e.getCause() instanceof VirtualMachineError) {
throw (VirtualMachineError) e.getCause();
} else {
throw e;
}
}
} catch (VirtualMachineError e) {
// OutOfMemoryError will be will handled in main()
throw e;
} catch (Throwable t) {
System.out.println("(fcsh) need to repeat command");
launchFcshWithoutFix(args);
}
}
}
private static Class getEntryPointClass(final String flexSdkVersion) {
if (flexSdkVersion.startsWith("3")) {
return findClass("com.intellij.flex.SimpleShellWithFix");
} else if (flexSdkVersion.startsWith("4")) {
if (flexSdkVersion.startsWith("4.0") || flexSdkVersion.startsWith("4.1")) {
return getFcshWithFixClass(flexSdkVersion, "com.intellij.flex.Fcsh4WithFix", FLEX_4_SDK_BUILDS_THAT_HAVE_FIX);
} else if (flexSdkVersion.startsWith("4.5") || flexSdkVersion.startsWith("4.6")) {
return getFcshWithFixClass(flexSdkVersion, "com.intellij.flex.Fcsh45WithFix", FLEX_45_SDK_BUILDS_THAT_HAVE_FIX);
}
}
return null;
}
private static Class getFcshWithFixClass(final String flexSdkVersion, final String classNamePrefix, final int[] sdkBuildsThatHaveFix) {
try {
int build = Integer.parseInt(flexSdkVersion.substring(flexSdkVersion.indexOf(" build ") + " build ".length()));
if (build == 0) {
System.out.println("Warning: Flex SDK build can not be 0.");
} else {
for (int i = 0; i < sdkBuildsThatHaveFix.length; i++) {
int buildWithFix = sdkBuildsThatHaveFix[i];
if (sdkBuildsThatHaveFix.length == i + 1 || build < sdkBuildsThatHaveFix[i + 1]) {
return findClass(classNamePrefix + buildWithFix);
}
}
}
} catch (NumberFormatException ignored) {
}
return null;
}
private static String getFlexSdkVersion() {
String version = readVersionUsingAPI();
if (!checkFlexSdkVersion(version)) {
version = readVersionFromFlexSdkDescriptionXml();
}
return checkFlexSdkVersion(version) ? version : null;
}
private static boolean checkFlexSdkVersion(final String flexSdkVersion) {
return flexSdkVersion != null && flexSdkVersion.matches("[0-9][.][0-9].* build [0-9]+");
}
private static String readVersionUsingAPI() {
String version = null;
try {
final Class versionInfoClass = findClass("flex2.tools.VersionInfo");
final Method buildMessageMethod = versionInfoClass == null ? null : versionInfoClass.getMethod("buildMessage");
version = buildMessageMethod == null ? null : (String) buildMessageMethod.invoke(null);
if (version != null && version.startsWith("Version ")) {
version = version.substring("Version ".length());
}
} catch (Throwable ignored) {
}
return version;
}
private static String readVersionFromFlexSdkDescriptionXml() {
final String flexSdkHome = System.getProperty("application.home");
if (flexSdkHome == null) {
return null;
}
final File flexSdkDescriptionFile = new File(flexSdkHome + File.separatorChar + "flex-sdk-description.xml");
if (!flexSdkDescriptionFile.exists()) {
return null;
}
final String versionElement = "<flex-sdk-description><version>";
final String buildElement = "<flex-sdk-description><build>";
FileInputStream xmlInputStream = null;
try {
xmlInputStream = new FileInputStream(flexSdkDescriptionFile);
final Map<String, List<String>> versionInfo = findXMLElements(xmlInputStream, Arrays.asList(versionElement, buildElement));
return (versionInfo.get(versionElement).isEmpty() ? null : versionInfo.get(versionElement).get(0)) +
(versionInfo.get(buildElement).isEmpty() ? "" : (" build " + versionInfo.get(buildElement).get(0)));
} catch (IOException e) {
return null;
} finally {
close(xmlInputStream);
}
}
/**
* Looks through input stream containing XML document and finds all entries of XML elements listed in <code>xmlElements</code>.
* Content of these elements is put to result map. XML namespaces are not taken into consideration.
*
* @param xmlInputStream input stream with xml content to parse
* @param xmlElements list of XML elements to look for.
* Format is: <code>"<root_element><child_element><subelement_to_look_for>"</code>.
* Listed XML elements MUST NOT contain subelements, otherwise result is undefined
* @return map, keys are XML elements listed in <code>xmlElements</code>,
* values are all entries of respective element (may be empty list)
*/
private static Map<String, List<String>> findXMLElements(final InputStream xmlInputStream, final List<String> xmlElements) {
final Map<String, List<String>> resultMap = new HashMap<String, List<String>>();
for (final String element : xmlElements) {
resultMap.put(element, new ArrayList<String>());
}
try {
SAXParserFactory.newInstance().newSAXParser().parse(xmlInputStream, new DefaultHandler() {
private String currentElement = "";
private StringBuilder currentElementContent = new StringBuilder();
public void startElement(final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException {
currentElement += "<" + qName + ">";
}
public void endElement(final String uri, final String localName, final String qName) throws SAXException {
if (xmlElements.contains(currentElement)) {
resultMap.get(currentElement).add(currentElementContent.toString());
currentElementContent.delete(0, currentElementContent.length());
}
assert currentElement.endsWith("<" + qName + ">");
currentElement = currentElement.substring(0, currentElement.length() - (qName.length() + 2));
}
public void characters(char[] ch, int start, int length) throws SAXException {
if (xmlElements.contains(currentElement)) {
currentElementContent.append(ch, start, length);
}
}
});
} catch (SAXException ignored) {
} catch (IOException ignored) {
} catch (ParserConfigurationException ignored) {
}
return resultMap;
}
private static Class findClass(final String className) {
try {
return Class.forName(className);
} catch (Throwable t) {
return null;
}
}
private static void close(final Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException ignored) {
}
}
}
}