package processing.app; import java.io.*; import java.util.*; import processing.app.contrib.*; import processing.core.*; import processing.data.StringDict; import processing.data.StringList; public class Library extends LocalContribution { static final String[] platformNames = PConstants.platformNames; //protected File folder; // /path/to/shortname protected File libraryFolder; // shortname/library protected File examplesFolder; // shortname/examples protected File referenceFile; // shortname/reference/index.html /** * Subfolder for grouping libraries in a menu. Basic subfolder support * is provided so that some organization can be done in the import menu. * (This is the replacement for the "library compilation" type.) */ protected String group; /** Packages provided by this library. */ StringList packageList; /** Per-platform exports for this library. */ HashMap<String, String[]> exportList; /** Applet exports (cross-platform by definition). */ String[] appletExportList; /** Android exports (single platform for now, may not exist). */ String[] androidExportList; /** True if there are separate 32/64 bit for the specified platform index. */ boolean[] multipleArch = new boolean[platformNames.length]; /** * For runtime, the native library path for this platform. e.g. on Windows 64, * this might be the windows64 subfolder with the library. */ String nativeLibraryPath; static public final String propertiesFileName = "library.properties"; /** * Filter to pull out just files and none of the platform-specific * directories, and to skip export.txt. As of 2.0a2, other directories are * included, because we need things like the 'plugins' subfolder w/ video. */ static FilenameFilter standardFilter = new FilenameFilter() { public boolean accept(File dir, String name) { // skip .DS_Store files, .svn folders, etc if (name.charAt(0) == '.') return false; if (name.equals("CVS")) return false; if (name.equals("export.txt")) return false; File file = new File(dir, name); // return (!file.isDirectory()); if (file.isDirectory()) { if (name.equals("macosx")) return false; if (name.equals("macosx32")) return false; if (name.equals("macosx64")) return false; if (name.equals("windows")) return false; if (name.equals("windows32")) return false; if (name.equals("windows64")) return false; if (name.equals("linux")) return false; if (name.equals("linux32")) return false; if (name.equals("linux64")) return false; if (name.equals("linux-armv6hf")) return false; if (name.equals("linux-arm64")) return false; if (name.equals("android")) return false; } return true; } }; static FilenameFilter jarFilter = new FilenameFilter() { public boolean accept(File dir, String name) { if (name.charAt(0) == '.') return false; // skip ._blah.jar crap on OS X if (new File(dir, name).isDirectory()) return false; String lc = name.toLowerCase(); return lc.endsWith(".jar") || lc.endsWith(".zip"); } }; static public Library load(File folder) { try { return new Library(folder); // } catch (IgnorableException ig) { // Base.log(ig.getMessage()); } catch (Error err) { // Handles UnsupportedClassVersionError and others err.printStackTrace(); } catch (Exception ex) { ex.printStackTrace(); } return null; } public Library(File folder) { this(folder, null); } private Library(File folder, String groupName) { super(folder); this.group = groupName; libraryFolder = new File(folder, "library"); examplesFolder = new File(folder, "examples"); referenceFile = new File(folder, "reference/index.html"); handle(); } /** * Handles all the Java-specific parsing for library handling. */ protected void handle() { File exportSettings = new File(libraryFolder, "export.txt"); StringDict exportTable = exportSettings.exists() ? Util.readSettings(exportSettings) : new StringDict(); exportList = new HashMap<String, String[]>(); // get the list of files just in the library root String[] baseList = libraryFolder.list(standardFilter); // System.out.println("Loading " + name + "..."); // PApplet.println(baseList); String appletExportStr = exportTable.get("applet"); if (appletExportStr != null) { appletExportList = PApplet.splitTokens(appletExportStr, ", "); } else { appletExportList = baseList; } String androidExportStr = exportTable.get("android"); if (androidExportStr != null) { androidExportList = PApplet.splitTokens(androidExportStr, ", "); } else { androidExportList = baseList; } // for the host platform, need to figure out what's available File nativeLibraryFolder = libraryFolder; String hostPlatform = Platform.getName(); // System.out.println("1 native lib folder now " + nativeLibraryFolder); // see if there's a 'windows', 'macosx', or 'linux' folder File hostLibrary = new File(libraryFolder, hostPlatform); if (hostLibrary.exists()) { nativeLibraryFolder = hostLibrary; } // System.out.println("2 native lib folder now " + nativeLibraryFolder); // check for bit-specific version, e.g. on windows, check if there // is a window32 or windows64 folder (on windows) hostLibrary = new File(libraryFolder, hostPlatform + Platform.getNativeBits()); if (hostLibrary.exists()) { nativeLibraryFolder = hostLibrary; } // System.out.println("3 native lib folder now " + nativeLibraryFolder); if (hostPlatform.equals("linux") && System.getProperty("os.arch").equals("arm")) { hostLibrary = new File(libraryFolder, "linux-armv6hf"); if (hostLibrary.exists()) { nativeLibraryFolder = hostLibrary; } } if (hostPlatform.equals("linux") && System.getProperty("os.arch").equals("aarch64")) { hostLibrary = new File(libraryFolder, "linux-arm64"); if (hostLibrary.exists()) { nativeLibraryFolder = hostLibrary; } } // save that folder for later use nativeLibraryPath = nativeLibraryFolder.getAbsolutePath(); // for each individual platform that this library supports, figure out what's around for (int i = 1; i < platformNames.length; i++) { String platformName = platformNames[i]; String platformName32 = platformName + "32"; String platformName64 = platformName + "64"; String platformNameArmv6hf = platformName + "-armv6hf"; String platformNameArm64 = platformName + "-arm64"; // First check for things like 'application.macosx=' or 'application.windows32' in the export.txt file. // These will override anything in the platform-specific subfolders. String platformAll = exportTable.get("application." + platformName); String[] platformList = platformAll == null ? null : PApplet.splitTokens(platformAll, ", "); String platform32 = exportTable.get("application." + platformName + "32"); String[] platformList32 = platform32 == null ? null : PApplet.splitTokens(platform32, ", "); String platform64 = exportTable.get("application." + platformName + "64"); String[] platformList64 = platform64 == null ? null : PApplet.splitTokens(platform64, ", "); String platformArmv6hf = exportTable.get("application." + platformName + "-armv6hf"); String[] platformListArmv6hf = platformArmv6hf == null ? null : PApplet.splitTokens(platformArmv6hf, ", "); String platformArm64 = exportTable.get("application." + platformName + "-arm64"); String[] platformListArm64 = platformArm64 == null ? null : PApplet.splitTokens(platformArm64, ", "); // If nothing specified in the export.txt entries, look for the platform-specific folders. if (platformAll == null) { platformList = listPlatformEntries(libraryFolder, platformName, baseList); } if (platform32 == null) { platformList32 = listPlatformEntries(libraryFolder, platformName32, baseList); } if (platform64 == null) { platformList64 = listPlatformEntries(libraryFolder, platformName64, baseList); } if (platformListArmv6hf == null) { platformListArmv6hf = listPlatformEntries(libraryFolder, platformNameArmv6hf, baseList); } if (platformListArm64 == null) { platformListArm64 = listPlatformEntries(libraryFolder, platformNameArm64, baseList); } if (platformList32 != null || platformList64 != null || platformListArmv6hf != null || platformListArm64 != null) { multipleArch[i] = true; } // if there aren't any relevant imports specified or in their own folders, // then use the baseList (root of the library folder) as the default. if (platformList == null && platformList32 == null && platformList64 == null && platformListArmv6hf == null && platformListArm64 == null) { exportList.put(platformName, baseList); } else { // once we've figured out which side our bread is buttered on, save it. // (also concatenate the list of files in the root folder as well if (platformList != null) { exportList.put(platformName, platformList); } if (platformList32 != null) { exportList.put(platformName32, platformList32); } if (platformList64 != null) { exportList.put(platformName64, platformList64); } if (platformListArmv6hf != null) { exportList.put(platformNameArmv6hf, platformListArmv6hf); } if (platformListArm64 != null) { exportList.put(platformNameArm64, platformListArm64); } } } // for (String p : exportList.keySet()) { // System.out.println(p + " -> "); // PApplet.println(exportList.get(p)); // } // get the path for all .jar files in this code folder packageList = Util.packageListFromClassPath(getClassPath()); } /** * List who's inside a windows64, macosx, linux32, etc folder. */ static String[] listPlatformEntries(File libraryFolder, String folderName, String[] baseList) { File folder = new File(libraryFolder, folderName); if (folder.exists()) { String[] entries = folder.list(standardFilter); if (entries != null) { String[] outgoing = new String[entries.length + baseList.length]; for (int i = 0; i < entries.length; i++) { outgoing[i] = folderName + "/" + entries[i]; } // Copy the base libraries in there as well System.arraycopy(baseList, 0, outgoing, entries.length, baseList.length); return outgoing; } } return null; } static protected HashMap<String, Object> packageWarningMap = new HashMap<String, Object>(); /** * Add the packages provided by this library to the master list that maps * imports to specific libraries. * @param importToLibraryTable mapping from package names to Library objects */ // public void addPackageList(HashMap<String,Library> importToLibraryTable) { public void addPackageList(Map<String, List<Library>> importToLibraryTable) { // PApplet.println(packages); for (String pkg : packageList) { // pw.println(pkg + "\t" + libraryFolder.getAbsolutePath()); // PApplet.println(pkg + "\t" + getName()); // Library library = importToLibraryTable.get(pkg); List<Library> libraries = importToLibraryTable.get(pkg); if (libraries == null) { libraries = new ArrayList<Library>(); importToLibraryTable.put(pkg, libraries); } else { if (Base.DEBUG) { System.err.println("The library found in"); System.err.println(getPath()); System.err.println("conflicts with"); for (Library library : libraries) { System.err.println(library.getPath()); } System.err.println("which already define(s) the package " + pkg); System.err.println("If you have a line in your sketch that reads"); System.err.println("import " + pkg + ".*;"); System.err.println("Then you'll need to first remove one of those libraries."); System.err.println(); } } libraries.add(this); } } public boolean hasExamples() { return examplesFolder.exists(); } public File getExamplesFolder() { return examplesFolder; } public String getGroup() { return group; } public String getPath() { return folder.getAbsolutePath(); } public String getLibraryPath() { return libraryFolder.getAbsolutePath(); } public String getJarPath() { //return new File(folder, "library/" + name + ".jar").getAbsolutePath(); return new File(libraryFolder, folder.getName() + ".jar").getAbsolutePath(); } // this prepends a colon so that it can be appended to other paths safely public String getClassPath() { StringBuilder cp = new StringBuilder(); // PApplet.println(libraryFolder.getAbsolutePath()); // PApplet.println(libraryFolder.list()); String[] jarHeads = libraryFolder.list(jarFilter); for (String jar : jarHeads) { cp.append(File.pathSeparatorChar); cp.append(new File(libraryFolder, jar).getAbsolutePath()); } File nativeLibraryFolder = new File(nativeLibraryPath); if (!libraryFolder.equals(nativeLibraryFolder)) { jarHeads = new File(nativeLibraryPath).list(jarFilter); for (String jar : jarHeads) { cp.append(File.pathSeparatorChar); cp.append(new File(nativeLibraryPath, jar).getAbsolutePath()); } } //cp.setLength(cp.length() - 1); // remove the last separator return cp.toString(); } public String getNativePath() { // PApplet.println("native lib folder " + nativeLibraryPath); return nativeLibraryPath; } // public String[] getAppletExports() { // return appletExportList; // } protected File[] wrapFiles(String[] list) { File[] outgoing = new File[list.length]; for (int i = 0; i < list.length; i++) { outgoing[i] = new File(libraryFolder, list[i]); } return outgoing; } /** * Applet exports don't go by platform, since by their nature applets are * meant to be cross-platform. Technically, you could have a situation where * you want to export applet code for different platforms, but it's too * obscure a case that we're not interested in supporting it. */ public File[] getAppletExports() { return wrapFiles(appletExportList); } public File[] getApplicationExports(int platform, String variant) { String[] list = getApplicationExportList(platform, variant); return wrapFiles(list); } /** * Returns the necessary exports for the specified platform. * If no 32 or 64-bit version of the exports exists, it returns the version * that doesn't specify bit depth. */ public String[] getApplicationExportList(int platform, String variant) { String platformName = PConstants.platformNames[platform]; if (variant.equals("32")) { String[] pieces = exportList.get(platformName + "32"); if (pieces != null) return pieces; } else if (variant.equals("64")) { String[] pieces = exportList.get(platformName + "64"); if (pieces != null) return pieces; } else if (variant.equals("armv6hf")) { String[] pieces = exportList.get(platformName + "-armv6hf"); if (pieces != null) return pieces; } else if (variant.equals("arm64")) { String[] pieces = exportList.get(platformName + "-arm64"); if (pieces != null) return pieces; } return exportList.get(platformName); } public File[] getAndroidExports() { return wrapFiles(androidExportList); } // public boolean hasMultiplePlatforms() { // return false; // } public boolean hasMultipleArch(int platform) { return multipleArch[platform]; } public boolean supportsArch(int platform, String variant) { // If this is a universal library, or has no natives, then we're good. if (multipleArch[platform] == false) { return true; } return getApplicationExportList(platform, variant) != null; } static public boolean hasMultipleArch(int platform, List<Library> libraries) { for (Library library : libraries) { if (library.hasMultipleArch(platform)) { return true; } } return false; } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . static protected FilenameFilter junkFolderFilter = new FilenameFilter() { public boolean accept(File dir, String name) { // skip .DS_Store files, .svn folders, etc if (name.charAt(0) == '.') return false; if (name.equals("CVS")) return false; return (new File(dir, name).isDirectory()); } }; static public List<File> discover(File folder) { List<File> libraries = new ArrayList<File>(); String[] folderNames = folder.list(junkFolderFilter); // if a bad folder or something like that, this might come back null if (folderNames != null) { // alphabetize list, since it's not always alpha order // replaced hella slow bubble sort with this feller for 0093 Arrays.sort(folderNames, String.CASE_INSENSITIVE_ORDER); for (String potentialName : folderNames) { File baseFolder = new File(folder, potentialName); File libraryFolder = new File(baseFolder, "library"); File libraryJar = new File(libraryFolder, potentialName + ".jar"); // If a .jar file of the same prefix as the folder exists // inside the 'library' subfolder of the sketch if (libraryJar.exists()) { String sanityCheck = Sketch.sanitizeName(potentialName); if (sanityCheck.equals(potentialName)) { libraries.add(baseFolder); } else { String mess = "The library \"" + potentialName + "\" cannot be used.\n" + "Library names must contain only basic letters and numbers.\n" + "(ASCII only and no spaces, and it cannot start with a number)"; Messages.showMessage("Ignoring bad library name", mess); continue; } /* } else { // maybe it's a JS library // TODO this should be in a better location File jsLibrary = new File(libraryFolder, potentialName + ".js"); if (jsLibrary.exists()) { libraries.add(baseFolder); } */ } } } return libraries; } static public List<Library> list(File folder) { List<Library> libraries = new ArrayList<Library>(); List<File> librariesFolders = new ArrayList<File>(); librariesFolders.addAll(discover(folder)); for (File baseFolder : librariesFolders) { libraries.add(new Library(baseFolder)); } // Support libraries inside of one level of subfolders? I believe this was // the compromise for supporting library groups, but probably a bad idea // because it's not compatible with the Manager. String[] folderNames = folder.list(junkFolderFilter); if (folderNames != null) { for (String subfolderName : folderNames) { File subfolder = new File(folder, subfolderName); if (!librariesFolders.contains(subfolder)) { List<File> discoveredLibFolders = discover(subfolder); for (File discoveredFolder : discoveredLibFolders) { libraries.add(new Library(discoveredFolder, subfolderName)); } } } } return libraries; } public ContributionType getType() { return ContributionType.LIBRARY; } /** * Returns the object stored in the referenceFile field, which contains an * instance of the file object representing the index file of the reference * * @return referenceFile */ public File getReferenceIndexFile() { return referenceFile; } /** * Tests whether the reference's index file indicated by referenceFile exists. * * @return true if and only if the file denoted by referenceFile exists; false * otherwise. */ public boolean hasReference() { return referenceFile.exists(); } }