package com.intellij.flex.maven; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Build; import org.apache.maven.plugin.Mojo; import org.apache.maven.project.MavenProject; import net.flexmojos.oss.compiler.*; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.util.*; public class IdeaConfiguratorFlexmojos5 implements FlexConfigGenerator { protected static final String PATH_ELEMENT = "path-element"; protected static final String FILE_SPECS = "file-specs"; protected static final String LOCAL_FONTS_SNAPSHOT = "local-fonts-snapshot"; private static final String FONTS_SER = "fonts.ser"; protected final MavenSession session; protected final StringBuilder out; private Build build; private String classifier; protected final File outputDirectory; @SuppressWarnings("StaticNonFinalField") protected static File sharedFontsSer; @SuppressWarnings("StaticNonFinalField") protected static String sharedFontsSerPath; private boolean staticLinkRuntimeSharedLibrariesSpecified; public IdeaConfiguratorFlexmojos5(MavenSession session, File outputDirectory) { this.session = session; this.outputDirectory = outputDirectory; out = new StringBuilder(8192); out.append("<flex-config xmlns=\"http://www.adobe.com/2006/flex-config\">"); } @Override public void generate(Mojo configuration, File sourceFile) throws Exception { build(configuration, ICommandLineConfiguration.class, "\n\t", null); out.append("\n\t<file-specs>\n"); writeTag("\t", PATH_ELEMENT, sourceFile.getAbsolutePath(), FILE_SPECS); out.append("\n\t</file-specs>"); } @Override public void generate(Mojo configuration) throws Exception { build(configuration, ICompcConfiguration.class, "\n\t", null); } @Override public void preGenerate(MavenProject project, String classifier) throws IOException { this.classifier = classifier; build = project.getBuild(); } @Override public String postGenerate(MavenProject project) throws IOException { if (!staticLinkRuntimeSharedLibrariesSpecified) { out.append("\n<static-link-runtime-shared-libraries>false</static-link-runtime-shared-libraries>"); } out.append("\n</flex-config>"); final String configFile = getConfigFilePath(project, classifier); Utils.write(out, new File(outputDirectory, configFile)); return configFile; } protected String getConfigFilePath(MavenProject project, String classifier) { // artifact id is first in path - it is convenient for us StringBuilder pathBuilder = new StringBuilder(32).append(project.getArtifactId()).append('-').append(project.getGroupId()); if (classifier != null) { pathBuilder.append('-').append(classifier); } return pathBuilder.append(".xml").toString(); } @SuppressWarnings("ConstantConditions") private <E> void build(E configuration, Class<?> configClass, String indent, String configurationName) throws Exception { boolean parentTagWritten = configurationName == null; final Method[] methods = configClass.getMethods(); Arrays.sort(methods, new Comparator<Method>() { @Override public int compare(Method o1, Method o2) { return o1.getName().compareTo(o2.getName()); } }); for (Method method : methods) { method.setAccessible(true); if (method.getParameterTypes().length != 0) { continue; } final String methodName = method.getName(); if (methodName.equals("getLoadConfig") || methodName.equals( "getDumpConfig") || "metadata".equals(configurationName) && methodName.equals("getDate")) { continue; } final Object value = method.invoke(configuration); if (value == null) { continue; } if ((methodName.equals("getFixedLiteralVector") || methodName.equals("getHeadlessServer")) && !((Boolean)value)) { continue; } // https://youtrack.jetbrains.com/issue/IDEA-108572 if (!staticLinkRuntimeSharedLibrariesSpecified && methodName.equals("getStaticLinkRuntimeSharedLibraries")) { staticLinkRuntimeSharedLibrariesSpecified = true; } if (!parentTagWritten) { parentTagWritten = true; out.append(indent, 0, indent.length() - 1).append('<').append(configurationName).append('>'); } final Class<?> returnType = method.getReturnType(); final String name = camelCaseToSnake(methodName, true); if (value instanceof IFlexConfiguration) { build(value, returnType, indent + '\t', name.substring(0, name.length() - 14)); } else if (configuration instanceof IASDocConfiguration && "footer".equals(name)) { // todo throw new UnsupportedOperationException(); } else if (value instanceof IRuntimeSharedLibraryPath || value instanceof IRuntimeSharedLibraryPath[]) { final IRuntimeSharedLibraryPath[] values; if (returnType.isArray()) { //noinspection ConstantConditions values = (IRuntimeSharedLibraryPath[])value; } else { //noinspection ConstantConditions values = new IRuntimeSharedLibraryPath[]{(IRuntimeSharedLibraryPath)value}; } for (IRuntimeSharedLibraryPath arg : values) { out.append("\n\t<").append(name).append(">\n\t\t<path-element>").append(arg.pathElement()).append("</path-element>"); //noinspection unchecked for (Map.Entry<String, String> entry : (Set<Map.Entry<String, String>>)arg.rslUrl().entrySet()) { out.append("\n\t\t<rsl-url>").append(entry.getKey()).append("</rsl-url>"); if (entry.getValue() != null) { out.append("\n\t\t<policy-file-url>").append(entry.getValue()).append("</policy-file-url>"); } } out.append("\n\t</").append(name).append('>'); } } else if (value instanceof IFlexArgument || value instanceof IFlexArgument[]) { IFlexArgument[] values; Class<?> type = returnType; if (type.isArray()) { //noinspection ConstantConditions values = (IFlexArgument[])value; type = returnType.getComponentType(); } else { //noinspection ConstantConditions values = new IFlexArgument[]{(IFlexArgument)value}; type = returnType; } String[] order = (String[])type.getField("ORDER").get(returnType); for (IFlexArgument iFlexArgument : values) { out.append(indent).append('<').append(name).append('>'); for (String argMethodName : order) { Object argValue = type.getDeclaredMethod(argMethodName).invoke(iFlexArgument); if (argValue instanceof Collection<?> || argValue.getClass().isArray()) { Object[] subValues = argValue.getClass().isArray() ? (Object[])argValue : ((Collection<?>)argValue).toArray(); for (Object subArgValue : subValues) { writeTag(indent, argMethodName, subArgValue.toString(), name); } } else if (argValue instanceof Map<?, ?>) { Map<?, ?> map = (Map<?, ?>) argValue; for (Object argValue1 : map.entrySet()) { @SuppressWarnings("unchecked") Map.Entry<String, ?> entry = (Map.Entry<String, ?>) argValue1; if (entry.getValue() != null) { writeTag(indent, entry.getKey(), entry.getValue().toString(), name); } } } else if (argValue != null) { // IDEA-109745 String xmlSrgMethodName; if (argMethodName.equals("serialNumber") || name.equals("default-script-limits")) { xmlSrgMethodName = camelCaseToSnake(argMethodName, false); } else { xmlSrgMethodName = argMethodName; } writeTag(indent, xmlSrgMethodName, (String)argValue, name); } } out.append(indent).append("</").append(name).append('>'); } } else if (name.equals("load-externs") || configuration instanceof IMetadataConfiguration && (name.equals("language") || name.equals("creator") || name.equals("publisher") || name.equals("contributor"))) { for (String v : (String[])value) { out.append(indent).append('<').append(name).append('>').append(v).append("</").append(name).append('>'); } } else if (returnType.isArray() || value instanceof Collection<?>) { Object[] values = returnType.isArray() ? (Object[])value : ((Collection<?>)value).toArray(); // ability to compile pure AS3 project without themes - node must be present, but empty (relevant only for "theme") if (values.length == 0) { if (name.equals("theme") || name.equals("locale")) { out.append(indent).append('<').append(name).append("/>"); } } else { out.append(indent).append('<').append(name).append('>'); String childTagName = Utils.CHILD_TAG_NAME_MAP.get(name); if (childTagName == null) { childTagName = PATH_ELEMENT; } for (Object v : values) { if (v == null) { System.out.print('\n' + childTagName + " child value for " + name + " is null\n"); } else { writeTag(indent, childTagName, v.toString(), name); } } out.append(indent).append("</").append(name).append('>'); } } else { out.append(indent).append('<').append(name).append('>'); processValue(value.toString(), name); out.append("</").append(name).append('>'); } } if (parentTagWritten && configurationName != null) { out.append(indent, 0, indent.length() - 1).append("</").append(configurationName).append('>'); } } protected void processValue(String value, String name) throws IOException { // http://juick.com/develar/1363289 if (name.equals(LOCAL_FONTS_SNAPSHOT)) { final File fontsSer = new File(build.getOutputDirectory(), FONTS_SER); String defaultPath; // the same as flexmojos does try { defaultPath = fontsSer.getCanonicalPath(); } catch (IOException e) { defaultPath = fontsSer.getAbsolutePath(); } if (value.equals(defaultPath)) { if (sharedFontsSerPath == null) { sharedFontsSer = new File(outputDirectory, FONTS_SER); //noinspection NonThreadSafeLazyInitialization sharedFontsSerPath = sharedFontsSer.getPath(); if (!sharedFontsSer.exists()) { Utils.copyFile(fontsSer, sharedFontsSer); } } value = sharedFontsSerPath; } } out.append(value); } protected void writeTag(String indent, String name, String value, String parentName) throws IOException { out.append(indent).append("\t<").append(name).append('>').append(value).append("</").append(name).append('>'); } private static String camelCaseToSnake(final String s, boolean removePrefix) { StringBuilder builder = new StringBuilder(s.length() + 4 /* probable max hyphen count */); for (int i = removePrefix ? removePrefix(s) : 0, n = s.length(); i < n; i++) { char c = s.charAt(i); if (Character.isUpperCase(c)) { builder.append('-').append(Character.toLowerCase(c)); } else { builder.append(c); } } return builder.substring(builder.charAt(0) == '-' ? 1 : 0); } private static int removePrefix(String s) { int cut = 0; for (int i = 0; i < s.length(); i++) { if (Character.isUpperCase(s.charAt(i))) { cut = i; break; } } return cut; } }