package xapi.source; import xapi.inject.X_Inject; import xapi.source.api.HasQualifiedName; import xapi.source.api.IsType; import xapi.source.service.SourceService; import xapi.util.X_String; import xapi.util.api.Pair; import xapi.util.impl.PairBuilder; import static xapi.util.X_String.isEmpty; import javax.inject.Provider; import javax.validation.constraints.NotNull; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collections; public class X_Source { private X_Source() {} private static final String arrays = "\\[\\]"; private static final Provider<SourceService> service = X_Inject.singletonLazy(SourceService.class); public static IsType toType(Class<?> cls) { return service.get().toType(cls); } protected static IsType toType(String qualifiedName) { String pkg = toPackage(qualifiedName); return toType(pkg, pkg.length() == 0 ? qualifiedName : qualifiedName.substring(pkg.length()+1)); } public static IsType toType(String pkg, String enclosedName) { return service.get().toType(X_String.notNull(pkg).replace('/', '.'), enclosedName.replace('$', '.')); } /** * Send in com.pkg.Clazz$InnerClass * or com/pkg/Clazz$InnerClass * Get back Pair<"com.pkg", "Clazz.InnerClass" * @param qualifiedBinaryName - The cls.getCanonicalName, or cls.getQualifiedBinaryName * @return - A pair of source names ('.' delimited), [pac.kage, Enclosing.Name] */ public static IsType binaryToSource(String qualifiedBinaryName) { int arrDepth = 0; while(qualifiedBinaryName.charAt(0) == '[') { arrDepth++; qualifiedBinaryName = qualifiedBinaryName.substring(1); } qualifiedBinaryName = qualifiedBinaryName.replace('/', '.'); int lastPkg = qualifiedBinaryName.lastIndexOf('.'); String pkg; if (lastPkg == -1) { pkg = ""; } else { pkg = qualifiedBinaryName.substring(0, lastPkg); assert pkg.equals(pkg.toLowerCase()) : "Either you are using an uppercase letter in your package name (stop that!)\n" + "or you are sending an inner class using period encoding instead of $ (also stop that!)\n" + "You sent "+qualifiedBinaryName+"; expected com.package.OuterClass$InnerClass"; } String enclosed = X_Modifier.toEnclosingType(qualifiedBinaryName.substring(lastPkg+1)); return toType(pkg, X_Modifier.addArrayBrackets(enclosed, arrDepth)); } public static String stripJarName(String loc) { int ind = loc.indexOf("jar!"); if (ind == -1) return loc; return stripFileName(loc.substring(0, ind+3)); } public static String stripFileName(String loc) { return loc.startsWith("file:") ? loc.substring(5) : loc; } public static String stripClassExtension(String loc) { return loc.endsWith(".class") ? loc.substring(0, loc.length()-6) : loc; } public static String primitiveToObject(String datatype) { if ("int".equals(datatype)) return "Integer"; if ("char".equals(datatype)) return "Character"; return Character.toUpperCase(datatype.charAt(0)) + datatype.substring(1); } public static String[] toStringCanonical( Class<?> ... classes) { // TODO move this to service, so gwt can use seedId or something more deterministic String[] names = new String[classes.length]; for (int i = classes.length; i-->0;) names[i] = classes[i].getCanonicalName(); return names; } public static String[] toStringBinary( Class<?> ... classes) { // TODO move this to service, so gwt can use seedId or something more deterministic String[] names = new String[classes.length]; for (int i = classes.length; i-->0;) names[i] = classes[i].getName(); return names; } public static String toStringEnclosed(Class<?> cls) { return cls.getCanonicalName().replace(cls.getPackage().getName()+".", ""); } public static URL[] getUrls(ClassLoader classLoader) { ArrayList<URL> urls = new ArrayList<URL>(); ClassLoader system = ClassLoader.getSystemClassLoader(); while (classLoader != null && classLoader != system) { if (classLoader instanceof URLClassLoader) { Collections.addAll(urls, ((URLClassLoader)classLoader).getURLs()); classLoader = classLoader.getParent(); } } return urls.toArray(new URL[urls.size()]); } public static URL classToUrl(String binaryName, ClassLoader loader) { return loader.getResource(binaryName.replace('.', '/')+".class"); } public static String classToEnclosedSourceName( Class<?> cls) { return cls.getCanonicalName().replace(cls.getPackage().getName()+".", ""); } public static boolean typesEqual(IsType[] one, IsType[] two) { if (one == null) return two == null; if (one.length != two.length) return false; for (int i = 0, m = one.length; i < m; ++i) { if (!one[i].equals(two[i])) return false; } return true; } public static boolean typesEqual(IsType[] one, Class<?> ... two) { if (one == null) return two == null; if (one.length != two.length) return false; for (int i = 0, m = one.length; i < m; ++i) { if (!one[i].getQualifiedName().equals(two[i].getCanonicalName())) return false; } return true; } public static boolean typesEqual(Class<?>[] one, IsType ... two) { if (one == null) return two == null; if (one.length != two.length) return false; for (int i = 0, m = one.length; i < m; ++i) { if (!one[i].getCanonicalName().equals(two[i].getQualifiedName())) return false; } return true; } public static boolean typesEqual(Class<?>[] one, Class<?> ... two) { if (one == null) return two == null; if (one.length != two.length) return false; for (int i = 0, m = one.length; i < m; ++i) { if (one[i] != two[i]) return false; } return true; } public static boolean typesAssignable(Class<?>[] subtypes, Class<?> ... supertypes) { if (subtypes == null) return supertypes == null; if (subtypes.length != supertypes.length) return false; for (int i = 0, m = subtypes.length; i < m; ++i) { if (!subtypes[i].isAssignableFrom(supertypes[i])) return false; } return true; } public static IsType[] toTypes(String[] from) { IsType[] types = new IsType[from.length]; for (int i = 0, m = from.length; i < m; ++i) { String type = from[i]; types[i] = toType(type); } return types; } public static String toPackage(String cls) { int lastPeriod = cls.lastIndexOf('.'); while (lastPeriod != -1) { if (Character.isUpperCase(cls.charAt(lastPeriod+1))) lastPeriod = cls.lastIndexOf('.', lastPeriod-1); else break; } if (lastPeriod == -1) return ""; else return cls.substring(0, lastPeriod); } public static Pair<String, Integer> extractArrayDepth(String from) { int arrayDepth = 0; while (from.matches(".*"+arrays)) { arrayDepth ++; from = from.replaceFirst(arrays, ""); } return PairBuilder.pairOf(from, arrayDepth); } public static boolean isJavaLangObject(HasQualifiedName type) { return type.getQualifiedName().equals("java.lang.Object"); } public static String qualifiedName(String pkg, String enclosed) { return isEmpty(pkg) ? enclosed : isEmpty(enclosed) ? pkg : enclosed.startsWith(pkg+".") ? enclosed : pkg + "." + enclosed; } public static String[] splitClassName(String providerName) { int was, is = was = providerName.lastIndexOf('.'); if (was == -1) { return new String[]{"", providerName}; } while (is != -1) { if (Character.isLowerCase(providerName.charAt(is+1))) { // the dot is before a lower-case value. Use the next position as match return new String[]{ providerName.substring(0, was), providerName.substring(was+1) }; } else { was = is; is = providerName.lastIndexOf('.', was-1); } } if (Character.isLowerCase(providerName.charAt(0))) { return new String[]{ providerName.substring(0, was), providerName.substring(was+1) }; } return new String[]{"", providerName}; } public static String normalizeNewlines(String template) { // to remain GWT-compatible, we will use string regex methods. // a jvm-only implementation could be made using precompiled Patterns, // but it's likely not worth the nano seconds it will save. return template.replaceAll("\r\n?", "\n"); } public static String escape(final String unescaped) { int extra = 0; for (int in = 0, n = unescaped.length(); in < n; ++in) { switch (unescaped.charAt(in)) { case '\r': String normalized = normalizeNewlines(unescaped); if (!normalized.equals(unescaped)) { return escape(normalized); } case '\0': case '\n': case '\"': case '\\': ++extra; break; } } if (extra == 0) { return unescaped; } final char[] oldChars = unescaped.toCharArray(); final char[] newChars = new char[oldChars.length + extra]; for (int in = 0, out = 0, n = oldChars.length; in < n; ++in, ++out) { char c = oldChars[in]; switch (c) { case '\r': newChars[out++] = '\\'; c = 'r'; break; case '\0': newChars[out++] = '\\'; c = '0'; break; case '\n': newChars[out++] = '\\'; c = 'n'; break; case '\"': newChars[out++] = '\\'; c = '"'; break; case '\\': newChars[out++] = '\\'; c = '\\'; break; } newChars[out] = c; } return String.valueOf(newChars); } public static String toCamelCase(String name) { return name == null || name.isEmpty() ? "" : Character.toUpperCase(name.charAt(0)) + (name.length() > 1 ? name.substring(1) : ""); } public static String removePackage(String pkgName, @NotNull String typeName) { assert typeName != null : "Do not send null typenames to X_Source.removePackage"; return pkgName == null ? typeName : typeName.replace(pkgName + ".", ""); } public static String enclosedNameFlattened(String pkg, String fullyQualified) { String enclosed = removePackage(pkg, fullyQualified); return enclosed.replace('.', '_'); } public static String addLineNumbers(String src) { final String[] lines = src.split("\n"); int maxSize = (int)(Math.log10(lines.length) + 1); for (int i = 0; i < lines.length; i++) { String line = lines[i]; lines[i] = (i + 1) + X_String.repeat(" ", maxSize - i + 1) + line; } return X_String.join("\n", lines); } }