├── .gitignore ├── CHANGELOG.txt ├── INSTALL.txt ├── NOTES.txt ├── README.md ├── Rakefile ├── jruby_for_max.properties.example ├── jruby_for_max ├── jruby.advanced.maxhelp ├── jruby.error_window.maxpat ├── jruby.examples.handling-input.maxpat ├── jruby.examples.sending-output.maxpat ├── jruby.examples.using-files.maxpat ├── jruby.gem_manager.maxpat ├── jruby.help.open-example.maxpat ├── jruby.live.api.maxpat ├── jruby.maxhelp ├── jruby.seealso.maxpat └── lib │ ├── help │ ├── jruby_argv.rb │ ├── jruby_autowatch.rb │ ├── jruby_call_send.rb │ ├── jruby_file_example.rb │ ├── jruby_info_outlet.rb │ ├── jruby_inlets.rb │ ├── jruby_load_require.rb │ ├── jruby_scriptfile.rb │ ├── jruby_text_to.rb │ ├── jruby_variable_scope.rb │ └── jruby_version.rb │ ├── jruby_for_max │ ├── data_storage.rb │ ├── delay.rb │ ├── gem_manager.rb │ ├── live_api.rb │ ├── live_api │ │ ├── clip.rb │ │ ├── live_object.rb │ │ └── note.rb │ └── send_receive.rb │ ├── jruby_initialize.rb │ └── support │ └── gem_manager_interface.rb ├── lib ├── ant-junit.jar ├── jruby.jar ├── junit-4.5.jar └── max.jar ├── license ├── LICENSE-jruby_for_max.txt └── jruby │ ├── COPYING │ ├── COPYING.CPL │ ├── COPYING.GPL │ └── COPYING.LGPL ├── src ├── jruby.java └── jruby4max │ ├── maxsupport │ ├── Atomizer.java │ └── JRubyMaxObject.java │ ├── rubysupport │ ├── IScriptEvaluator.java │ ├── IdInUseException.java │ ├── InputConversionOption.java │ ├── MaxRubyAdapter.java │ ├── RubyException.java │ ├── RubyProperties.java │ ├── ScriptEvaluator.java │ └── ScriptEvaluatorManager.java │ └── util │ ├── DummyMaxObject.java │ ├── FileWatcher.java │ ├── GlobalVariableStore.java │ ├── LineBuilder.java │ ├── Logger.java │ ├── MappedSet.java │ ├── TextBlock.java │ ├── TextViewer.java │ └── Utils.java └── test ├── jruby4max ├── rubysupport │ └── ScriptEvaluatorManagerTest.java └── util │ └── MappedSetTest.java └── patches └── loadbang_test.maxpat /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | build_test 3 | download 4 | dist 5 | jruby4max.jar 6 | .DS_Store 7 | out 8 | *.iml 9 | .idea 10 | -------------------------------------------------------------------------------- /CHANGELOG.txt: -------------------------------------------------------------------------------- 1 | 2 | JRuby for Max changelog 3 | 4 | ****************************************************************************** 5 | * Version 1.0.2 * 6 | ****************************************************************************** 7 | 8 | * Upgrade JRuby to version 1.7.4 9 | 10 | * Added Ruby Gem manager patcher, for installing, updating, and deleting gems. 11 | 12 | 13 | ****************************************************************************** 14 | * Version 1.0.1 * 15 | ****************************************************************************** 16 | 17 | * Upgrade JRuby to version 1.6.7.2 18 | 19 | * Fixed a bug in JRubyForMax::LiveAPI::LiveObject that preventing receiving 20 | data from the Max patcher. 21 | 22 | ****************************************************************************** 23 | * Version 1.0 (since this project was forked from "ajm objects") * 24 | ****************************************************************************** 25 | 26 | * Ruby 1.9 is now the default interpreter mode (use @ruby_version 1.8 to change back to 1.8 if needed) 27 | 28 | * Upgraded JRuby to version 1.6.4 29 | 30 | * .rb extension is now optional for @file 31 | 32 | * A jruby.error_window patch has been provided to make it easy to display errors. 33 | This is especially useful in Max for Live where the Max window is often not visible. 34 | 35 | * Ruby evaluation is automatically deferred until the object has initialized, so you don't have 36 | to put a [deferlow] object after your [loadbang]s anymore 37 | 38 | * $0 and __FILE__ are now the absolute path to the @file, so you can determine the folder containing 39 | the @file (with File.dirname $0) 40 | 41 | * Current directory (Dir.pwd) is now set to the user's home directory instead of "/" 42 | 43 | * Added info outlet support. The info outlet also sends messages indicating various status changes, 44 | like "fileloaded". 45 | 46 | * @symbols_to was changed to @text_to and the behavior changed as follows: 47 | - @text_to conversions only applies to text input (not numbers) 48 | - the "string" conversion type now single-quotes the string 49 | - a new "interpolated" conversion type double-quotes the string 50 | 51 | * Added a JRubyForMax::SendReceive module containing methods for sending and receiving data between 52 | any jruby objects in Max or Max for Live. 53 | 54 | * Added a JRubyForMax::LiveAPI module to ease interacting with the Live API. 55 | The main use case so far is reading and writing MIDI clips. 56 | 57 | 58 | ****************************************************************************** 59 | * Version 0.9.2 (when the object was ajm.ruby from "ajm objects") * 60 | ****************************************************************************** 61 | 62 | * Upgraded JRuby to version 1.5.5 63 | 64 | 65 | ****************************************************************************** 66 | * Version 0.9 (when the object was ajm.ruby from "ajm objects") * 67 | ****************************************************************************** 68 | 69 | * Multiple inlets are now supported. This is the first argument to the object, 70 | and number of outlets is the second argument (it was previous the first). 71 | If you were using multiple outlets, you must now add a first argument for 72 | 1 inlet. So [mxj ajm.ruby 3] becomes [mxj ajm.ruby 1 3]. 73 | The inlet() method reports the current inlet in your Ruby script. 74 | 75 | * Evaluation by sending ruby code messages to the object's inlet now needs to be 76 | prepended with the 'eval' message. 77 | So instead of sending [puts 'hello'] you must now send [eval puts 'hello'] 78 | Adding a [prepend eval] before ajm.ruby's inlet is a simple way to update 79 | existing patches that relied on the old behavior. 80 | 81 | * @listproc is no longer supported. Any input that is a list automatically calls 82 | the inlet() method. Use the 'eval' message to evaluate Ruby code. 83 | 84 | * def list(*params) in Ruby has now been replace with def inlet(inlet_index, *params) 85 | There are also in0, in1, ... in9(*params) convenience methods. 86 | See the help patch for examples of usage. 87 | 88 | * @evaloutlet's default value has changed from 0 to -1 (no output) 89 | If you rely on evaluating scripts on-the-fly from Max messages, you either 90 | explicit call out0 to send output to the outlet, or expliclty set @evalout 0 91 | (note the preferred name of @evaloutlet is @evalout) 92 | 93 | * The @scriptfile attribute is now deprecated in favor of @file 94 | 95 | * The @evaloutlet attribute is now deprecated in favor of @evalout 96 | 97 | * No gems are included with ajm.ruby anymore 98 | 99 | * ajm.ruby now provides much more useful error messages 100 | 101 | * unlike the old list() method, the inlet() method uses a splat for it's parameters, 102 | so you can define this method like 103 | def inlet(inlet_index, param1, param2, param3) 104 | or 105 | def inlet(inlet_index, *params) 106 | to handle any number of parameters. 107 | This is true for the in0, in1, ..., in9 convenience methods too. 108 | 109 | * The directory of the @file is added to $LOAD_PATH so you can require 110 | files relative to it. 111 | 112 | * @files can be located relative to the current patch 113 | 114 | * Double clicking a script that's running a @file will open that file 115 | in the associated editor (based on your OS file associations) 116 | 117 | * A 'call' message was introduced, see the "receiving input from Max" 118 | section of the help patch 119 | 120 | * A 'send' message was introduced, see the "receiving input from Max" 121 | section of the help patch 122 | 123 | * the number of files installed with ajm.ruby has decreased significantly 124 | (we now use jruby-complete.jar instead of an unpacked ruby installation) 125 | 126 | * __FILE__ is now set correctly when using @file to load a script 127 | 128 | * A @ruby_version attribute was introduced to control the Ruby version on 129 | a per-object basis. Valid values are @ruby_version 1.9 and @ruby_version 1.8 130 | 1.8 is the default. 1.9 support is still under development by the JRuby 131 | developers so it may not be completely compatible with MRI Ruby 1.9. 132 | 133 | * Ruby symbols now have the ':' prefix when output to Max 134 | 135 | * JRuby upgraded to version 1.5.2 136 | 137 | 138 | ****************************************************************************** 139 | * Version 0.8.8 (when the object was ajm.ruby from "ajm objects") * 140 | ****************************************************************************** 141 | 142 | * JRuby upgraded to version 1.3.1 143 | 144 | * Enabled Ruby 1.9 support 145 | 146 | 147 | ****************************************************************************** 148 | * Version 0.8.6 (when the object was ajm.ruby from "ajm objects") * 149 | ****************************************************************************** 150 | 151 | * $max_object_map stores references to all ajm.ruby objects 152 | 153 | * max_object() lookup method (see help file) 154 | 155 | * $max_objects is an array containing all ajm.ruby objects in the current 156 | context 157 | 158 | * added global variable storage system, set_global() and get_global() methods 159 | 160 | * added convenience methods inlet_assist() and outlet_assist() 161 | 162 | * initialization code externalized to the script file ajm_ruby_initialize.rb 163 | 164 | * The $LOAD_PATH no longer includes all folders on Max's search path. 165 | 166 | * properties file introduced to expose configuration options: 167 | - ruby.initializers: initialization code for ajm.ruby 168 | - ruby.loadpath: additional paths to include on the $LOAD_PATH 169 | 170 | * Renamed some variables and methods to be follow standard Ruby naming 171 | convetions: 172 | - $MaxObject renamed to $max_object 173 | - setLocal() and getLocal() renamed to set_local() and get_local() 174 | The old names are still available for backward compatibility, but they are 175 | now deprecated and will be removed at some point in the future. 176 | 177 | * Ruby symbols are converted to Max Strings without a coercion warning 178 | 179 | * Fixed a bug outputting arrays with 3 or more levels of nesting 180 | 181 | * JRuby upgraded to version 1.1.5 182 | 183 | 184 | ****************************************************************************** 185 | * Version 0.8.1 (when the object was ajm.ruby from "ajm objects") * 186 | ****************************************************************************** 187 | 188 | * varargs support fixed for puts(), print(), and error() 189 | 190 | * @scriptfile and '@evaloutlet -1' avoid unnecessary conversion to Max Atoms 191 | (prevents pointless "coerced to string" messages in the Max window) 192 | 193 | * JRuby upgraded to version 1.1.2 194 | 195 | 196 | ****************************************************************************** 197 | * Version 0.8 (when the object was ajm.ruby from "ajm objects") * 198 | ****************************************************************************** 199 | 200 | * Upgraded to JRuby 1.1.1 from JRuby 1.1 RC1 201 | Improved performance/memory usage and thousands of compatibility fixes. 202 | See http://jruby.codehaus.org/ for more info. 203 | 204 | * Added scriptfile attribute - load a script from an external file 205 | 206 | * The $0 variable will be set to the absolute path of the scriptfile, or nil 207 | if no scriptfile is being used. 208 | 209 | * ARGV and $* can be used to access any additonal arguments to scriptfile. 210 | 211 | * Added context attribute - supports shared evaluator context between 212 | any sequencer or ajm.ruby objects 213 | 214 | * Improved message parsing behavior 215 | - fixed a bug parsing Max symbols containing spaces 216 | - the "text" message was added to better support textedit's one symbol 217 | output mode: [route text] should no longer be used before ajm.ruby 218 | 219 | * Improved search path behavior 220 | - Ruby search path now includes any folder on Max's search path. 221 | The search order is: 222 | (1) the java/lib/ruby directory structure (standard libraries) 223 | (2) Max's search path (see Options -> File Preferences) 224 | - Any file on the search path can be loaded/required by filename instead 225 | of the full path. 226 | 227 | * Gems can now be loaded. But first they must be installed via a seperate 228 | JRuby installation. See the NOTES section of the README for more info. 229 | 230 | * Initialization of the ruby evaluator is deferred until the first script is 231 | evaluated. This speeds up loading of patches with many ruby objects, but 232 | there will be a slight delay when the first script evaluates. 233 | 234 | * Added an autoinit attribute - use this to force initialization of the ruby 235 | evaluator when the patch loads, eating the cost of initialization up front. 236 | 237 | * Max lists sent to the inlet of [ajm.ruby] call a list() method that takes a 238 | single array as an argument: def list(array) ... 239 | The default implementation passes through the list. Redefine as desired. 240 | 241 | * The list() behavior can be disabled by setting @listproc false 242 | 243 | * Added methods out0(*params), out1, ..., out9 244 | as shortcuts for outlet(0, *params), etc 245 | 246 | * When nested arrays and hashes are coerced to Strings, the String should 247 | be an accurate representation of the datastructure (i.e. you can pass it 248 | to another ajm.ruby and it should evaluate correctly). 249 | 250 | * Unnecessary conversions to Atom types are now avoided when evaloutlet < 0 251 | 252 | * Long values outside the 32-bit integer range (-2147483648 to 2147483647) 253 | are now coerced to a String when outputting to Max to avoid wraparound. 254 | -------------------------------------------------------------------------------- /INSTALL.txt: -------------------------------------------------------------------------------- 1 | 2 | JRuby for Max 3 | 4 | Version @@VERSION 5 | 6 | Built @@BUILD_DATE 7 | 8 | https://github.com/adamjmurray/jruby_for_max 9 | 10 | Adam Murray (adam@compusition.com) 11 | 12 | ------------------------------------------------------------------------------ 13 | 14 | REQUIREMENTS 15 | * Max/MSP 5 or higher 16 | * Java 5 or higher (most computers today have this) 17 | 18 | ----------------------------------------------------------------------------- 19 | 20 | INSTALLATION 21 | 22 | (1) Put lib/jruby4max.jar and lib/jruby.jar in: 23 | Max6/Cycling '74/java/lib (or Max5/Cycling '74/java/lib for Max 5) 24 | 25 | (2) Put the jruby_for_max folder somewhere on your Max file search path. 26 | You can either: 27 | (a) Copy the folder to Max6/patches (or Max5/patches for Max 5) 28 | (b) or add the folder to the search path via Max's Options menu => File Preferences 29 | 30 | (3) Restart Max/MSP 31 | 32 | (4) To get started, open jruby_for_max/jruby.maxhelp 33 | or add the object [mxj jruby] to a patcher and right click it => Help 34 | 35 | ----------------------------------------------------------------------------- 36 | 37 | PROBLEMS? 38 | 39 | Check out the FAQ: https://github.com/adamjmurray/jruby_for_max/wiki/FAQ 40 | 41 | Log an issue: https://github.com/adamjmurray/jruby_for_max/issues 42 | 43 | Email me: adam@compusition.com 44 | -------------------------------------------------------------------------------- /NOTES.txt: -------------------------------------------------------------------------------- 1 | 2 | JRuby for Max 3 | 4 | Version @@VERSION 5 | 6 | http://compusition.com/web/software/maxmsp/ajm-objects 7 | 8 | Adam Murray (adam@compusition.com) 9 | 10 | 11 | See INSTALL.txt for installation instructions 12 | 13 | FAQ: https://github.com/adamjmurray/jruby_for_max/wiki/FAQ 14 | 15 | Examples: https://github.com/adamjmurray/jruby_for_max-examples 16 | 17 | ------------------------------------------------------------------------------ 18 | 19 | SOURCE CODE 20 | 21 | JRuby for Max is an open source project. The source code is located at: 22 | http://github.com/adamjmurray/jruby_for_max 23 | 24 | Feel free to hack away at it, contribute features, or whatever you want. 25 | Just mind my copyright and give credit where credit is due. 26 | 27 | ------------------------------------------------------------------------------ 28 | 29 | DEPENDENCIES 30 | 31 | JRuby (http://jruby.org/) 32 | The 100% Java implementation of Ruby. 33 | JRuby license info is under the license/jruby folder. 34 | You can find license info for the Ruby language at: 35 | http://www.ruby-lang.org/en/LICENSE.txt 36 | http://www.ruby-lang.org/en/COPYING.txt 37 | 38 | ------------------------------------------------------------------------------ 39 | 40 | Copyright (c) 2008-2011, Adam Murray (adam@compusition.com). 41 | 42 | All rights reserved. 43 | 44 | Redistribution and use of JRuby for Max in source and binary forms, with or 45 | without modification, are permitted provided that the following conditions 46 | are met: 47 | 48 | 1. Redistributions of source code must retain the above copyright 49 | notice, this list of conditions and the following disclaimer. 50 | 51 | 2. Redistributions in binary form must reproduce the above copyright 52 | notice, this list of conditions and the following disclaimer in 53 | the documentation and/or other materials provided with the 54 | distribution. 55 | 56 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 57 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 58 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 59 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 60 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 61 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 62 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 63 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 64 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 65 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 66 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JRuby for Max # 2 | 3 | Ruby support for [Max](http://cycling74.com/products/maxmspjitter/) and [Max for Live](http://www.ableton.com/products), built on top of [JRuby](http://jruby.org) 4 | 5 | 6 | ## Status ## 7 | 8 | 1.x version. Ready for general use. 9 | 10 | [DOWNLOAD IT HERE](https://s3hub-26970d107940297a9dae7a83104f92a9d30730d0d6103f4385a508b86.s3.amazonaws.com/jruby_for_max/jruby_for_max-1.0.2.zip) 11 | 12 | 13 | ## Author ## 14 | 15 | Adam Murray (adam@compusition.com) 16 | 17 | 18 | ## More Info ## 19 | 20 | [Home Page](http://compusition.com/web/software/maxmsp/jruby_for_max) 21 | 22 | [Project Page on Cycling '74](http://cycling74.com/toolbox/jruby-for-max/) 23 | 24 | [FAQ](http://github.com/adamjmurray/jruby_for_max/wiki/FAQ) 25 | 26 | [Examples](http://github.com/adamjmurray/jruby_for_max-examples) 27 | 28 | 29 | ## Questions / Problems? ## 30 | 31 | File an issue @ https://github.com/adamjmurray/jruby_for_max/issues or email me @ adam@compusition.com 32 | 33 | 34 | ## Building the project (for developers) 35 | 36 | 0. Download this project, either: 37 | * via the download link on [this project's github page](http://github.com/adamjmurray/jruby_for_max) 38 | * or via git: 39 | * If this is your first time getting the project with git, use: 40 | 41 | git clone http://github.com/adamjmurray/jruby_for_max.git 42 | 43 | * If you already cloned the project and want the latest version, from the project directory run: 44 | 45 | git pull 46 | 47 | 0. Go to the project folder on the command line: 48 | 49 | cd jruby_for_max 50 | 51 | 0. Run the rake 'dist' task to build the project: 52 | 53 | java -jar lib/jruby.jar -S rake dist 54 | 55 | 0. Take a look in dist/jruby\_for\_max-{version}/INSTALL.txt for installation instructions to start using 56 | it with Max/MSP or Max for Live. 57 | 58 | ### Notes for Building on Windows 59 | 60 | In order to build this project, you must install a JDK and have the JDK's "bin" folder on your path. 61 | You can check by typing "jar" on the command line. If it says something like "jar is not a recognized command", then you need to follow these steps (after installing a JDK if you haven't already done so). 62 | 63 | 0. Go to Control Panel -> System -> Advanced System Settings 64 | 0. Click "Advanced" tab -> Environment Variables... 65 | 0. Find the "Path" variable under System Variables and click Edit... 66 | 0. Go to the end of the variable value and add ";C:\Program Files\Java\jdk1.6.0_22\bin" (adjust the path as needed for your computer, and make sure you put a semicolon before this path and the preceding part of the variable value) 67 | 68 | Now if you open a command prompt and type "jar" you should see usage 69 | instructions for the jar command. That means your JDK and path are setup correctly, and you should be able to build this project. 70 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/clean' 2 | require 'tempfile' 3 | require 'rbconfig' 4 | 5 | PROJECT_VERSION = '1.0.2' 6 | BUILD_DATE = Time.now.utc.strftime '%B %d, %Y (%H:%M GMT)' 7 | MANIFEST = 8 | "Library: JRuby for Max 9 | Version: #{PROJECT_VERSION} 10 | Built-Date: #{BUILD_DATE} 11 | Author: Adam Murray 12 | URL: http://compusition.com 13 | " 14 | BASEDIR = File.dirname(__FILE__) 15 | def path_to path; File.join BASEDIR,path end 16 | 17 | LIB = path_to 'lib' 18 | PATCHES = path_to 'jruby_for_max' 19 | LICENSE = path_to 'license' 20 | BUILD = path_to 'build' 21 | TESTS_BUILD = path_to 'build_test' 22 | DIST = path_to 'dist' 23 | PROJ = "jruby_for_max-#{PROJECT_VERSION}" 24 | PACKAGE = File.join DIST,PROJ 25 | 26 | JAR = "jruby4max.jar" 27 | JAR_FILE = "#{LIB}/#{JAR}" 28 | 29 | SOURCES = FileList['src/**/*.java'] 30 | TESTS = FileList['test/**/*.java'] 31 | CLASSPATH = FileList["#{LIB}/**/*.jar"].exclude(/^#{JAR}$/) 32 | TESTS_CLASSPATH = CLASSPATH.clone.add(BUILD) 33 | 34 | WINDOWS = Config::CONFIG['host_os'] =~ /mswin/ 35 | CLASSPATH_SEPARATOR = if WINDOWS then ';' else ':' end 36 | 37 | ############################################################################## 38 | # TASK DEFINITIONS 39 | 40 | CLEAN.include BUILD, TESTS_BUILD, JAR_FILE, DIST 41 | CLOBBER.include 'out' # default IntelliJ build folder 42 | 43 | 44 | desc 'Compile java source files' 45 | task :compile do 46 | puts "Compiling java source files" 47 | javac CLASSPATH, SOURCES, BUILD 48 | end 49 | 50 | 51 | task :jar => [:clean, :compile] do 52 | manifest = Tempfile.new('manifest') 53 | write_file manifest, MANIFEST 54 | puts "Archiving build: #{JAR_FILE}" 55 | jar JAR_FILE, manifest, BUILD 56 | end 57 | 58 | 59 | task :package => [:jar] do 60 | puts "Preparing distribution" 61 | package_lib = File.join PACKAGE,'lib' 62 | mkdir_p package_lib 63 | 64 | # Collect the files 65 | FileList['*.txt', '*.example'].each do |filename| 66 | cp filename, PACKAGE 67 | end 68 | FileList["#{LIB}/jruby.jar", JAR_FILE].each do |filename| 69 | cp filename, package_lib 70 | end 71 | cp_r PATCHES, PACKAGE 72 | cp_r LICENSE, PACKAGE 73 | end 74 | 75 | 76 | task :replace_vars => [:package] do 77 | puts "Performing search and replace for the VERSION and BUILD_DATE variables" 78 | plaintext_filetypes = ['txt', 'maxpat', 'maxhelp'] 79 | files = FileList[ plaintext_filetypes.map{|type| "#{PACKAGE}/**/*.#{type}" } ] 80 | files.replace_all '@@VERSION', PROJECT_VERSION 81 | files.replace_all '@@BUILD_DATE', BUILD_DATE 82 | end 83 | 84 | 85 | desc 'Build distribution archive' 86 | task :dist => [:replace_vars] do 87 | mkdir_p DIST 88 | archive = "#{PROJ}.zip" 89 | puts "Archiving distribution: #{DIST}/#{archive}" 90 | 91 | # NOTE: this only works on OS X. It might work with the correct Cygwin setup on Windows but I never tested this. 92 | `cd #{DIST} && zip -l -m -r #{archive} #{PROJ} -x "*.DS_Store"` 93 | # The -l option converts newlines to crlf, which should display correctly on both OS X and Windows. 94 | # Otherwise, since I write these txt files on OS X, newlines would disappear when viewed in Notepad on Windows. 95 | rm_rf PACKAGE 96 | end 97 | 98 | 99 | task :compile_tests => [:compile] do 100 | puts 'Compiling java test files' 101 | javac TESTS_CLASSPATH, TESTS, TESTS_BUILD 102 | end 103 | 104 | desc 'Run unit tests' 105 | task :test => [:compile_tests] do 106 | puts 'Running tests' 107 | junit_class_path = TESTS_CLASSPATH.clone.add(TESTS_BUILD) 108 | test_classes = FileList["#{TESTS_BUILD}/**/*.class"] 109 | test_classnames = test_classes.map{|path| path.sub("#{TESTS_BUILD}/", '').sub(".class", '').gsub('/', '.') } 110 | puts java junit_class_path, 'org.junit.runner.JUnitCore', test_classnames 111 | end 112 | 113 | 114 | 115 | ############################################################################## 116 | # SUPPORT CODE: 117 | 118 | def write_file file, contents 119 | File.open(file.path, 'w') {|io| io.write contents } 120 | end 121 | 122 | def java classpath, main_class, args 123 | `java -classpath #{classpath.join CLASSPATH_SEPARATOR} #{main_class} #{args}` 124 | end 125 | 126 | def javac classpath, src_files, dst_folder 127 | mkdir_p dst_folder 128 | `javac -classpath #{classpath.join CLASSPATH_SEPARATOR} -d #{dst_folder} -g -source 1.5 -target 1.5 #{src_files}` 129 | end 130 | 131 | def jar filename, manifest, dst_folder 132 | `jar cvfm #{filename} #{manifest.path} -C #{dst_folder} .` 133 | end 134 | 135 | 136 | class FileList 137 | # Replaces all occurrences of token with value 138 | def replace_all(token, value) 139 | self.each do |filename| 140 | next if File.directory? filename 141 | contents = IO.read filename 142 | if contents.include? token 143 | contents.gsub! token, value 144 | File.open(filename, 'w') do |io| 145 | io.write contents 146 | end 147 | end 148 | end 149 | end 150 | end 151 | 152 | -------------------------------------------------------------------------------- /jruby_for_max.properties.example: -------------------------------------------------------------------------------- 1 | ############################# 2 | # Options for JRuby for Max # 3 | ############################# 4 | 5 | # To use: 6 | # - Rename this file to jruby_for_max.properties 7 | # - Put it on the Max file search path 8 | # - Restart Max (and restart again each time you change this file) 9 | 10 | 11 | ruby.loadpaths = 12 | # A semicolon-separated list of absolute paths to append to the default $LOAD_PATH. 13 | # You can use this to add gems or other projects installed elsewhere. 14 | # Default value is nothing. 15 | 16 | 17 | # jruby.home = /Users/adam/lib/jruby-1.5.5 18 | # If you want to install a lot of gems: instead of adding a loadpath for each one, 19 | # you can install them into a separate jruby installation and set jruby.home to 20 | # run against that jruby instead of the built-in one. 21 | # This variable is not set by default. 22 | 23 | 24 | 25 | ruby.initializers = jruby_initialize.rb 26 | # A semicolon-separated list of files. 27 | # These files will be run in the order listed whenever a new jruby context is initialized. 28 | # Default value is jruby_initialize.rb 29 | -------------------------------------------------------------------------------- /jruby_for_max/jruby.examples.using-files.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 5, 6 | "minor" : 1, 7 | "revision" : 9 8 | } 9 | , 10 | "rect" : [ 681.0, 64.0, 732.0, 403.0 ], 11 | "bglocked" : 0, 12 | "defrect" : [ 681.0, 64.0, 732.0, 403.0 ], 13 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 14 | "openinpresentation" : 0, 15 | "default_fontsize" : 12.0, 16 | "default_fontface" : 0, 17 | "default_fontname" : "Arial", 18 | "gridonopen" : 0, 19 | "gridsize" : [ 15.0, 15.0 ], 20 | "gridsnaponopen" : 0, 21 | "toolbarvisible" : 1, 22 | "boxanimatetime" : 200, 23 | "imprint" : 0, 24 | "enablehscroll" : 1, 25 | "enablevscroll" : 1, 26 | "devicewidth" : 0.0, 27 | "boxes" : [ { 28 | "box" : { 29 | "bgcolor" : [ 1.0, 0.988235, 0.988235, 1.0 ], 30 | "fontname" : "Verdana", 31 | "fontsize" : 11.0, 32 | "id" : "obj-44", 33 | "maxclass" : "message", 34 | "numinlets" : 2, 35 | "numoutlets" : 1, 36 | "outlettype" : [ "" ], 37 | "patching_rect" : [ 329.0, 108.0, 209.0, 18.0 ] 38 | } 39 | 40 | } 41 | , { 42 | "box" : { 43 | "fontname" : "Verdana", 44 | "fontsize" : 11.0, 45 | "id" : "obj-42", 46 | "maxclass" : "newobj", 47 | "numinlets" : 1, 48 | "numoutlets" : 0, 49 | "patching_rect" : [ 474.0, 265.0, 36.0, 20.0 ], 50 | "text" : "print" 51 | } 52 | 53 | } 54 | , { 55 | "box" : { 56 | "fontname" : "Verdana", 57 | "fontsize" : 11.0, 58 | "hidden" : 1, 59 | "id" : "obj-39", 60 | "maxclass" : "newobj", 61 | "numinlets" : 1, 62 | "numoutlets" : 1, 63 | "outlettype" : [ "" ], 64 | "patching_rect" : [ 159.0, 107.0, 83.0, 20.0 ], 65 | "text" : "loadmess set" 66 | } 67 | 68 | } 69 | , { 70 | "box" : { 71 | "bgcolor" : [ 1.0, 0.988235, 0.988235, 1.0 ], 72 | "fontname" : "Verdana", 73 | "fontsize" : 11.0, 74 | "id" : "obj-40", 75 | "maxclass" : "message", 76 | "numinlets" : 2, 77 | "numoutlets" : 1, 78 | "outlettype" : [ "" ], 79 | "patching_rect" : [ 26.0, 82.0, 209.0, 18.0 ], 80 | "text" : "\"Hello from ajm_scriptfile.rb\"" 81 | } 82 | 83 | } 84 | , { 85 | "box" : { 86 | "fontname" : "Verdana", 87 | "fontsize" : 11.0, 88 | "id" : "obj-3", 89 | "linecount" : 2, 90 | "maxclass" : "comment", 91 | "numinlets" : 1, 92 | "numoutlets" : 0, 93 | "patching_rect" : [ 329.0, 44.0, 314.0, 33.0 ], 94 | "text" : "An easy way to develop scripts is with @file and @autowatch. And double click ajm.ruby to edit the file!" 95 | } 96 | 97 | } 98 | , { 99 | "box" : { 100 | "color" : [ 1.0, 0.890196, 0.090196, 1.0 ], 101 | "fontname" : "Verdana", 102 | "fontsize" : 11.0, 103 | "id" : "obj-8", 104 | "maxclass" : "newobj", 105 | "numinlets" : 1, 106 | "numoutlets" : 1, 107 | "outlettype" : [ "" ], 108 | "patching_rect" : [ 329.0, 77.0, 310.0, 20.0 ], 109 | "text" : "mxj jruby @file jruby_autowatch.rb @autowatch 1" 110 | } 111 | 112 | } 113 | , { 114 | "box" : { 115 | "color" : [ 1.0, 0.890196, 0.090196, 1.0 ], 116 | "fontname" : "Verdana", 117 | "fontsize" : 11.0, 118 | "id" : "obj-11", 119 | "maxclass" : "newobj", 120 | "numinlets" : 1, 121 | "numoutlets" : 1, 122 | "outlettype" : [ "" ], 123 | "patching_rect" : [ 28.0, 52.0, 211.0, 20.0 ], 124 | "text" : "mxj jruby @file jruby_scriptfile.rb" 125 | } 126 | 127 | } 128 | , { 129 | "box" : { 130 | "fontname" : "Verdana", 131 | "fontsize" : 11.0, 132 | "hidden" : 1, 133 | "id" : "obj-38", 134 | "maxclass" : "newobj", 135 | "numinlets" : 1, 136 | "numoutlets" : 1, 137 | "outlettype" : [ "" ], 138 | "patching_rect" : [ 304.0, 392.0, 83.0, 20.0 ], 139 | "text" : "loadmess set" 140 | } 141 | 142 | } 143 | , { 144 | "box" : { 145 | "bgcolor" : [ 1.0, 0.988235, 0.988235, 1.0 ], 146 | "fontname" : "Verdana", 147 | "fontsize" : 11.0, 148 | "id" : "obj-41", 149 | "maxclass" : "message", 150 | "numinlets" : 2, 151 | "numoutlets" : 1, 152 | "outlettype" : [ "" ], 153 | "patching_rect" : [ 297.0, 366.0, 179.0, 18.0 ] 154 | } 155 | 156 | } 157 | , { 158 | "box" : { 159 | "fontname" : "Verdana", 160 | "fontsize" : 11.0, 161 | "id" : "obj-4", 162 | "maxclass" : "message", 163 | "numinlets" : 2, 164 | "numoutlets" : 1, 165 | "outlettype" : [ "" ], 166 | "patching_rect" : [ 487.0, 215.0, 106.0, 18.0 ], 167 | "text" : "file jruby_argv.rb" 168 | } 169 | 170 | } 171 | , { 172 | "box" : { 173 | "fontname" : "Verdana", 174 | "fontsize" : 11.0, 175 | "id" : "obj-5", 176 | "maxclass" : "message", 177 | "numinlets" : 2, 178 | "numoutlets" : 1, 179 | "outlettype" : [ "" ], 180 | "patching_rect" : [ 474.0, 191.0, 252.0, 18.0 ], 181 | "text" : "file jruby_argv.rb 99 'oneword' \"two words\"" 182 | } 183 | 184 | } 185 | , { 186 | "box" : { 187 | "color" : [ 1.0, 0.890196, 0.090196, 1.0 ], 188 | "fontname" : "Verdana", 189 | "fontsize" : 11.0, 190 | "id" : "obj-6", 191 | "maxclass" : "newobj", 192 | "numinlets" : 1, 193 | "numoutlets" : 1, 194 | "outlettype" : [ "" ], 195 | "patching_rect" : [ 474.0, 240.0, 225.0, 20.0 ], 196 | "text" : "mxj jruby @file jruby_argv.rb :mute" 197 | } 198 | 199 | } 200 | , { 201 | "box" : { 202 | "fontname" : "Verdana", 203 | "fontsize" : 11.0, 204 | "id" : "obj-12", 205 | "linecount" : 2, 206 | "maxclass" : "comment", 207 | "numinlets" : 1, 208 | "numoutlets" : 0, 209 | "patching_rect" : [ 28.0, 259.0, 463.0, 33.0 ], 210 | "text" : "if it's in the same folder as the patch or \non the Max file path (see Options -> File Preferences) ..." 211 | } 212 | 213 | } 214 | , { 215 | "box" : { 216 | "fontname" : "Verdana", 217 | "fontsize" : 11.0, 218 | "id" : "obj-13", 219 | "maxclass" : "comment", 220 | "numinlets" : 1, 221 | "numoutlets" : 0, 222 | "patching_rect" : [ 30.0, 237.0, 139.0, 20.0 ], 223 | "text" : ".. or just a filename," 224 | } 225 | 226 | } 227 | , { 228 | "box" : { 229 | "fontname" : "Verdana", 230 | "fontsize" : 11.0, 231 | "id" : "obj-14", 232 | "maxclass" : "comment", 233 | "numinlets" : 1, 234 | "numoutlets" : 0, 235 | "patching_rect" : [ 25.0, 149.0, 202.0, 20.0 ], 236 | "text" : "Load a file with an absolute path..." 237 | } 238 | 239 | } 240 | , { 241 | "box" : { 242 | "fontname" : "Verdana", 243 | "fontsize" : 11.0, 244 | "id" : "obj-17", 245 | "maxclass" : "message", 246 | "numinlets" : 2, 247 | "numoutlets" : 1, 248 | "outlettype" : [ "" ], 249 | "patching_rect" : [ 165.0, 175.0, 223.0, 18.0 ], 250 | "text" : "file /Users/username/path/to/script.rb" 251 | } 252 | 253 | } 254 | , { 255 | "box" : { 256 | "fontname" : "Verdana", 257 | "fontsize" : 11.0, 258 | "id" : "obj-18", 259 | "maxclass" : "message", 260 | "numinlets" : 2, 261 | "numoutlets" : 1, 262 | "outlettype" : [ "" ], 263 | "patching_rect" : [ 219.0, 236.0, 129.0, 18.0 ], 264 | "text" : "file jruby_scriptfile.rb" 265 | } 266 | 267 | } 268 | , { 269 | "box" : { 270 | "fontname" : "Verdana", 271 | "fontsize" : 11.0, 272 | "id" : "obj-19", 273 | "maxclass" : "message", 274 | "numinlets" : 2, 275 | "numoutlets" : 1, 276 | "outlettype" : [ "" ], 277 | "patching_rect" : [ 329.0, 309.0, 32.0, 18.0 ], 278 | "text" : "file" 279 | } 280 | 281 | } 282 | , { 283 | "box" : { 284 | "fontname" : "Verdana", 285 | "fontsize" : 11.0, 286 | "id" : "obj-20", 287 | "maxclass" : "newobj", 288 | "numinlets" : 1, 289 | "numoutlets" : 2, 290 | "outlettype" : [ "", "bang" ], 291 | "patching_rect" : [ 69.0, 172.0, 69.0, 20.0 ], 292 | "text" : "opendialog" 293 | } 294 | 295 | } 296 | , { 297 | "box" : { 298 | "id" : "obj-21", 299 | "maxclass" : "button", 300 | "numinlets" : 1, 301 | "numoutlets" : 1, 302 | "outlettype" : [ "bang" ], 303 | "patching_rect" : [ 31.0, 179.0, 27.0, 27.0 ] 304 | } 305 | 306 | } 307 | , { 308 | "box" : { 309 | "fontname" : "Verdana", 310 | "fontsize" : 11.0, 311 | "id" : "obj-22", 312 | "maxclass" : "newobj", 313 | "numinlets" : 1, 314 | "numoutlets" : 1, 315 | "outlettype" : [ "" ], 316 | "patching_rect" : [ 69.0, 196.0, 76.0, 20.0 ], 317 | "text" : "prepend file" 318 | } 319 | 320 | } 321 | , { 322 | "box" : { 323 | "fontname" : "Verdana", 324 | "fontsize" : 11.0, 325 | "id" : "obj-23", 326 | "maxclass" : "comment", 327 | "numinlets" : 1, 328 | "numoutlets" : 0, 329 | "patching_rect" : [ 25.0, 309.0, 268.0, 20.0 ], 330 | "text" : ".. or just the file message to browse to a file." 331 | } 332 | 333 | } 334 | , { 335 | "box" : { 336 | "fontface" : 1, 337 | "fontname" : "Verdana", 338 | "fontsize" : 12.0, 339 | "id" : "obj-25", 340 | "maxclass" : "comment", 341 | "numinlets" : 1, 342 | "numoutlets" : 0, 343 | "patching_rect" : [ 27.0, 17.0, 475.0, 21.0 ], 344 | "text" : "The @file attribute and 'file' message runs Ruby code from a file." 345 | } 346 | 347 | } 348 | , { 349 | "box" : { 350 | "fontname" : "Verdana", 351 | "fontsize" : 11.0, 352 | "id" : "obj-26", 353 | "maxclass" : "comment", 354 | "numinlets" : 1, 355 | "numoutlets" : 0, 356 | "patching_rect" : [ 140.0, 176.0, 24.0, 20.0 ], 357 | "text" : "or" 358 | } 359 | 360 | } 361 | , { 362 | "box" : { 363 | "fontname" : "Verdana", 364 | "fontsize" : 11.0, 365 | "id" : "obj-28", 366 | "linecount" : 2, 367 | "maxclass" : "comment", 368 | "numinlets" : 1, 369 | "numoutlets" : 0, 370 | "patching_rect" : [ 504.0, 357.0, 166.0, 33.0 ], 371 | "text" : "@verbose prints when a file loads (in the Max window)" 372 | } 373 | 374 | } 375 | , { 376 | "box" : { 377 | "color" : [ 1.0, 0.890196, 0.090196, 1.0 ], 378 | "fontname" : "Verdana", 379 | "fontsize" : 11.0, 380 | "id" : "obj-29", 381 | "maxclass" : "newobj", 382 | "numinlets" : 1, 383 | "numoutlets" : 1, 384 | "outlettype" : [ "" ], 385 | "patching_rect" : [ 398.0, 335.0, 158.0, 20.0 ], 386 | "text" : "mxj jruby @verbose 1" 387 | } 388 | 389 | } 390 | , { 391 | "box" : { 392 | "bgcolor" : [ 0.913725, 0.964706, 0.952941, 1.0 ], 393 | "border" : 1, 394 | "id" : "obj-30", 395 | "maxclass" : "panel", 396 | "numinlets" : 1, 397 | "numoutlets" : 0, 398 | "patching_rect" : [ 26.0, 149.0, 374.0, 75.0 ] 399 | } 400 | 401 | } 402 | , { 403 | "box" : { 404 | "bgcolor" : [ 0.92549, 0.92549, 0.992157, 1.0 ], 405 | "border" : 1, 406 | "id" : "obj-31", 407 | "maxclass" : "panel", 408 | "numinlets" : 1, 409 | "numoutlets" : 0, 410 | "patching_rect" : [ 25.0, 232.0, 374.0, 67.0 ] 411 | } 412 | 413 | } 414 | , { 415 | "box" : { 416 | "bgcolor" : [ 0.87451, 0.898039, 0.784314, 1.0 ], 417 | "border" : 1, 418 | "id" : "obj-32", 419 | "maxclass" : "panel", 420 | "numinlets" : 1, 421 | "numoutlets" : 0, 422 | "patching_rect" : [ 25.0, 306.0, 372.0, 27.0 ] 423 | } 424 | 425 | } 426 | , { 427 | "box" : { 428 | "fontname" : "Verdana Bold", 429 | "fontsize" : 11.0, 430 | "id" : "obj-34", 431 | "maxclass" : "comment", 432 | "numinlets" : 1, 433 | "numoutlets" : 0, 434 | "patching_rect" : [ 473.0, 170.0, 132.0, 20.0 ], 435 | "text" : "Arguments:" 436 | } 437 | 438 | } 439 | ], 440 | "lines" : [ { 441 | "patchline" : { 442 | "destination" : [ "obj-40", 1 ], 443 | "hidden" : 0, 444 | "midpoints" : [ 37.5, 80.0, 225.5, 80.0 ], 445 | "source" : [ "obj-11", 0 ] 446 | } 447 | 448 | } 449 | , { 450 | "patchline" : { 451 | "destination" : [ "obj-29", 0 ], 452 | "hidden" : 0, 453 | "midpoints" : [ 174.5, 212.0, 407.5, 212.0 ], 454 | "source" : [ "obj-17", 0 ] 455 | } 456 | 457 | } 458 | , { 459 | "patchline" : { 460 | "destination" : [ "obj-29", 0 ], 461 | "hidden" : 0, 462 | "midpoints" : [ 228.5, 257.0, 407.5, 257.0 ], 463 | "source" : [ "obj-18", 0 ] 464 | } 465 | 466 | } 467 | , { 468 | "patchline" : { 469 | "destination" : [ "obj-29", 0 ], 470 | "hidden" : 0, 471 | "midpoints" : [ 338.5, 328.0, 407.5, 328.0 ], 472 | "source" : [ "obj-19", 0 ] 473 | } 474 | 475 | } 476 | , { 477 | "patchline" : { 478 | "destination" : [ "obj-22", 0 ], 479 | "hidden" : 0, 480 | "midpoints" : [ ], 481 | "source" : [ "obj-20", 0 ] 482 | } 483 | 484 | } 485 | , { 486 | "patchline" : { 487 | "destination" : [ "obj-20", 0 ], 488 | "hidden" : 0, 489 | "midpoints" : [ 40.5, 210.0, 62.5, 210.0, 62.5, 168.0, 78.5, 168.0 ], 490 | "source" : [ "obj-21", 0 ] 491 | } 492 | 493 | } 494 | , { 495 | "patchline" : { 496 | "destination" : [ "obj-29", 0 ], 497 | "hidden" : 0, 498 | "midpoints" : [ 78.5, 219.0, 407.5, 219.0 ], 499 | "source" : [ "obj-22", 0 ] 500 | } 501 | 502 | } 503 | , { 504 | "patchline" : { 505 | "destination" : [ "obj-41", 1 ], 506 | "hidden" : 0, 507 | "midpoints" : [ 407.5, 360.0, 466.5, 360.0 ], 508 | "source" : [ "obj-29", 0 ] 509 | } 510 | 511 | } 512 | , { 513 | "patchline" : { 514 | "destination" : [ "obj-41", 0 ], 515 | "hidden" : 1, 516 | "midpoints" : [ ], 517 | "source" : [ "obj-38", 0 ] 518 | } 519 | 520 | } 521 | , { 522 | "patchline" : { 523 | "destination" : [ "obj-40", 0 ], 524 | "hidden" : 1, 525 | "midpoints" : [ ], 526 | "source" : [ "obj-39", 0 ] 527 | } 528 | 529 | } 530 | , { 531 | "patchline" : { 532 | "destination" : [ "obj-44", 0 ], 533 | "hidden" : 1, 534 | "midpoints" : [ ], 535 | "source" : [ "obj-39", 0 ] 536 | } 537 | 538 | } 539 | , { 540 | "patchline" : { 541 | "destination" : [ "obj-6", 0 ], 542 | "hidden" : 0, 543 | "midpoints" : [ 496.5, 235.5, 483.5, 235.5 ], 544 | "source" : [ "obj-4", 0 ] 545 | } 546 | 547 | } 548 | , { 549 | "patchline" : { 550 | "destination" : [ "obj-6", 0 ], 551 | "hidden" : 0, 552 | "midpoints" : [ ], 553 | "source" : [ "obj-5", 0 ] 554 | } 555 | 556 | } 557 | , { 558 | "patchline" : { 559 | "destination" : [ "obj-42", 0 ], 560 | "hidden" : 0, 561 | "midpoints" : [ ], 562 | "source" : [ "obj-6", 0 ] 563 | } 564 | 565 | } 566 | , { 567 | "patchline" : { 568 | "destination" : [ "obj-44", 1 ], 569 | "hidden" : 0, 570 | "midpoints" : [ 338.5, 102.5, 528.5, 102.5 ], 571 | "source" : [ "obj-8", 0 ] 572 | } 573 | 574 | } 575 | ] 576 | } 577 | 578 | } 579 | -------------------------------------------------------------------------------- /jruby_for_max/jruby.help.open-example.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "appversion" : { 5 | "major" : 5, 6 | "minor" : 1, 7 | "revision" : 9 8 | } 9 | , 10 | "rect" : [ 410.0, 660.0, 416.0, 317.0 ], 11 | "bglocked" : 0, 12 | "defrect" : [ 410.0, 660.0, 416.0, 317.0 ], 13 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 14 | "openinpresentation" : 1, 15 | "default_fontsize" : 11.1, 16 | "default_fontface" : 0, 17 | "default_fontname" : "Verdana", 18 | "gridonopen" : 0, 19 | "gridsize" : [ 15.0, 15.0 ], 20 | "gridsnaponopen" : 0, 21 | "toolbarvisible" : 1, 22 | "boxanimatetime" : 200, 23 | "imprint" : 0, 24 | "enablehscroll" : 1, 25 | "enablevscroll" : 1, 26 | "devicewidth" : 0.0, 27 | "boxes" : [ { 28 | "box" : { 29 | "bgcolor" : [ 1.0, 1.0, 1.0, 1.0 ], 30 | "bgovercolor" : [ 0.752941, 0.976471, 1.0, 1.0 ], 31 | "border" : 3, 32 | "bordercolor" : [ 0.156863, 0.8, 0.54902, 1.0 ], 33 | "borderoncolor" : [ 0.156863, 0.8, 0.54902, 1.0 ], 34 | "fontname" : "Arial", 35 | "fontsize" : 12.0, 36 | "id" : "obj-1", 37 | "maxclass" : "textbutton", 38 | "numinlets" : 1, 39 | "numoutlets" : 3, 40 | "outlettype" : [ "", "", "int" ], 41 | "patching_rect" : [ 18.0, 64.0, 104.0, 23.0 ], 42 | "presentation" : 1, 43 | "presentation_rect" : [ 0.0, 0.0, 103.0, 22.0 ], 44 | "prototypename" : "help-green-more-info-button", 45 | "rounded" : 8.0, 46 | "text" : "#1", 47 | "texton" : "Stop" 48 | } 49 | 50 | } 51 | , { 52 | "box" : { 53 | "fontname" : "Verdana", 54 | "fontsize" : 11.1, 55 | "id" : "obj-7", 56 | "maxclass" : "newobj", 57 | "numinlets" : 1, 58 | "numoutlets" : 1, 59 | "outlettype" : [ "bang" ], 60 | "patching_rect" : [ 18.0, 4.0, 61.0, 20.0 ], 61 | "text" : "loadbang" 62 | } 63 | 64 | } 65 | , { 66 | "box" : { 67 | "fontname" : "Verdana", 68 | "fontsize" : 11.1, 69 | "id" : "obj-6", 70 | "maxclass" : "message", 71 | "numinlets" : 2, 72 | "numoutlets" : 1, 73 | "outlettype" : [ "" ], 74 | "patching_rect" : [ 18.0, 101.0, 92.0, 18.0 ], 75 | "text" : "loadunique #2" 76 | } 77 | 78 | } 79 | , { 80 | "box" : { 81 | "fontname" : "Verdana", 82 | "fontsize" : 11.1, 83 | "id" : "obj-14", 84 | "maxclass" : "newobj", 85 | "numinlets" : 1, 86 | "numoutlets" : 1, 87 | "outlettype" : [ "" ], 88 | "patching_rect" : [ 18.0, 127.0, 56.0, 20.0 ], 89 | "text" : "pcontrol" 90 | } 91 | 92 | } 93 | , { 94 | "box" : { 95 | "fontname" : "Verdana", 96 | "fontsize" : 11.1, 97 | "id" : "obj-2", 98 | "maxclass" : "message", 99 | "numinlets" : 2, 100 | "numoutlets" : 1, 101 | "outlettype" : [ "" ], 102 | "patching_rect" : [ 18.0, 34.0, 52.0, 18.0 ], 103 | "text" : "text #1" 104 | } 105 | 106 | } 107 | ], 108 | "lines" : [ { 109 | "patchline" : { 110 | "destination" : [ "obj-6", 0 ], 111 | "hidden" : 0, 112 | "midpoints" : [ ], 113 | "source" : [ "obj-1", 0 ] 114 | } 115 | 116 | } 117 | , { 118 | "patchline" : { 119 | "destination" : [ "obj-1", 0 ], 120 | "hidden" : 0, 121 | "midpoints" : [ ], 122 | "source" : [ "obj-2", 0 ] 123 | } 124 | 125 | } 126 | , { 127 | "patchline" : { 128 | "destination" : [ "obj-14", 0 ], 129 | "hidden" : 0, 130 | "midpoints" : [ ], 131 | "source" : [ "obj-6", 0 ] 132 | } 133 | 134 | } 135 | , { 136 | "patchline" : { 137 | "destination" : [ "obj-2", 0 ], 138 | "hidden" : 0, 139 | "midpoints" : [ ], 140 | "source" : [ "obj-7", 0 ] 141 | } 142 | 143 | } 144 | ] 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /jruby_for_max/jruby.live.api.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 228.0, 166.0, 250.0, 240.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 228.0, 166.0, 250.0, 240.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 11.0, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "enablehscroll" : 1, 19 | "enablevscroll" : 1, 20 | "devicewidth" : 0.0, 21 | "boxes" : [ { 22 | "box" : { 23 | "fontname" : "Arial Bold", 24 | "fontsize" : 10.0, 25 | "id" : "obj-13", 26 | "maxclass" : "newobj", 27 | "numinlets" : 1, 28 | "numoutlets" : 2, 29 | "outlettype" : [ "", "" ], 30 | "patching_rect" : [ 117.5, 84.0, 32.5, 18.0 ], 31 | "text" : "t s s" 32 | } 33 | 34 | } 35 | , { 36 | "box" : { 37 | "fontname" : "Arial Bold", 38 | "fontsize" : 10.0, 39 | "id" : "obj-57", 40 | "maxclass" : "newobj", 41 | "numinlets" : 1, 42 | "numoutlets" : 1, 43 | "outlettype" : [ "" ], 44 | "patching_rect" : [ 55.5, 152.0, 107.0, 18.0 ], 45 | "text" : "prepend send #1 set" 46 | } 47 | 48 | } 49 | , { 50 | "box" : { 51 | "fontname" : "Arial Bold", 52 | "fontsize" : 10.0, 53 | "id" : "obj-53", 54 | "maxclass" : "newobj", 55 | "numinlets" : 2, 56 | "numoutlets" : 1, 57 | "outlettype" : [ "" ], 58 | "patching_rect" : [ 55.5, 119.0, 59.0, 18.0 ], 59 | "text" : "live.object" 60 | } 61 | 62 | } 63 | , { 64 | "box" : { 65 | "fontname" : "Arial Bold", 66 | "fontsize" : 10.0, 67 | "id" : "obj-48", 68 | "maxclass" : "newobj", 69 | "numinlets" : 1, 70 | "numoutlets" : 3, 71 | "outlettype" : [ "", "", "" ], 72 | "patching_rect" : [ 117.5, 63.0, 51.0, 18.0 ], 73 | "text" : "live.path" 74 | } 75 | 76 | } 77 | , { 78 | "box" : { 79 | "comment" : "live.object", 80 | "id" : "obj-17", 81 | "maxclass" : "inlet", 82 | "numinlets" : 0, 83 | "numoutlets" : 1, 84 | "outlettype" : [ "" ], 85 | "patching_rect" : [ 55.5, 32.0, 25.0, 25.0 ] 86 | } 87 | 88 | } 89 | , { 90 | "box" : { 91 | "comment" : "live.path", 92 | "id" : "obj-18", 93 | "maxclass" : "inlet", 94 | "numinlets" : 0, 95 | "numoutlets" : 1, 96 | "outlettype" : [ "" ], 97 | "patching_rect" : [ 117.5, 32.0, 25.0, 25.0 ] 98 | } 99 | 100 | } 101 | , { 102 | "box" : { 103 | "comment" : "to jruby", 104 | "id" : "obj-19", 105 | "maxclass" : "outlet", 106 | "numinlets" : 1, 107 | "numoutlets" : 0, 108 | "patching_rect" : [ 55.5, 180.5, 25.0, 25.0 ] 109 | } 110 | 111 | } 112 | ], 113 | "lines" : [ { 114 | "patchline" : { 115 | "destination" : [ "obj-53", 1 ], 116 | "hidden" : 0, 117 | "midpoints" : [ ], 118 | "source" : [ "obj-13", 1 ] 119 | } 120 | 121 | } 122 | , { 123 | "patchline" : { 124 | "destination" : [ "obj-57", 0 ], 125 | "hidden" : 0, 126 | "midpoints" : [ 127.0, 142.0, 65.0, 142.0 ], 127 | "source" : [ "obj-13", 0 ] 128 | } 129 | 130 | } 131 | , { 132 | "patchline" : { 133 | "destination" : [ "obj-53", 0 ], 134 | "hidden" : 0, 135 | "midpoints" : [ ], 136 | "source" : [ "obj-17", 0 ] 137 | } 138 | 139 | } 140 | , { 141 | "patchline" : { 142 | "destination" : [ "obj-48", 0 ], 143 | "hidden" : 0, 144 | "midpoints" : [ ], 145 | "source" : [ "obj-18", 0 ] 146 | } 147 | 148 | } 149 | , { 150 | "patchline" : { 151 | "destination" : [ "obj-13", 0 ], 152 | "hidden" : 0, 153 | "midpoints" : [ ], 154 | "source" : [ "obj-48", 0 ] 155 | } 156 | 157 | } 158 | , { 159 | "patchline" : { 160 | "destination" : [ "obj-57", 0 ], 161 | "hidden" : 0, 162 | "midpoints" : [ ], 163 | "source" : [ "obj-53", 0 ] 164 | } 165 | 166 | } 167 | , { 168 | "patchline" : { 169 | "destination" : [ "obj-19", 0 ], 170 | "hidden" : 0, 171 | "midpoints" : [ ], 172 | "source" : [ "obj-57", 0 ] 173 | } 174 | 175 | } 176 | ] 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /jruby_for_max/jruby.seealso.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 55.0, 77.0, 369.0, 467.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 55.0, 77.0, 369.0, 467.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 1, 9 | "default_fontsize" : 11.1, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Verdana", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "metadata" : [ ], 19 | "boxes" : [ { 20 | "box" : { 21 | "maxclass" : "newobj", 22 | "text" : "sprintf send %s.bring_to_front", 23 | "fontname" : "Verdana", 24 | "numinlets" : 1, 25 | "patching_rect" : [ 104.0, 227.0, 183.0, 20.0 ], 26 | "fontsize" : 11.1, 27 | "numoutlets" : 1, 28 | "id" : "obj-27", 29 | "outlettype" : [ "" ] 30 | } 31 | 32 | } 33 | , { 34 | "box" : { 35 | "maxclass" : "message", 36 | "text" : "help $1", 37 | "fontname" : "Verdana", 38 | "numinlets" : 2, 39 | "patching_rect" : [ 110.0, 395.0, 52.0, 18.0 ], 40 | "fontsize" : 11.1, 41 | "numoutlets" : 1, 42 | "id" : "obj-6", 43 | "outlettype" : [ "" ] 44 | } 45 | 46 | } 47 | , { 48 | "box" : { 49 | "maxclass" : "newobj", 50 | "text" : "t 0", 51 | "fontname" : "Verdana", 52 | "numinlets" : 1, 53 | "patching_rect" : [ 110.0, 321.0, 26.0, 20.0 ], 54 | "fontsize" : 11.1, 55 | "numoutlets" : 1, 56 | "id" : "obj-20", 57 | "outlettype" : [ "int" ] 58 | } 59 | 60 | } 61 | , { 62 | "box" : { 63 | "maxclass" : "newobj", 64 | "text" : "t s b s 1", 65 | "fontname" : "Verdana", 66 | "numinlets" : 1, 67 | "patching_rect" : [ 77.0, 186.0, 59.5, 20.0 ], 68 | "fontsize" : 11.1, 69 | "numoutlets" : 4, 70 | "id" : "obj-19", 71 | "outlettype" : [ "", "bang", "", "int" ] 72 | } 73 | 74 | } 75 | , { 76 | "box" : { 77 | "maxclass" : "newobj", 78 | "text" : "gate", 79 | "fontname" : "Verdana", 80 | "numinlets" : 2, 81 | "patching_rect" : [ 110.0, 364.0, 35.0, 20.0 ], 82 | "fontsize" : 11.1, 83 | "numoutlets" : 1, 84 | "id" : "obj-21", 85 | "outlettype" : [ "" ] 86 | } 87 | 88 | } 89 | , { 90 | "box" : { 91 | "maxclass" : "newobj", 92 | "text" : "forward", 93 | "fontname" : "Verdana", 94 | "numinlets" : 1, 95 | "patching_rect" : [ 104.0, 262.0, 53.0, 20.0 ], 96 | "fontsize" : 11.1, 97 | "numoutlets" : 0, 98 | "id" : "obj-15" 99 | } 100 | 101 | } 102 | , { 103 | "box" : { 104 | "maxclass" : "newobj", 105 | "text" : "pcontrol", 106 | "fontname" : "Verdana", 107 | "numinlets" : 1, 108 | "patching_rect" : [ 110.0, 425.0, 56.0, 20.0 ], 109 | "fontsize" : 11.1, 110 | "numoutlets" : 1, 111 | "id" : "obj-22", 112 | "outlettype" : [ "" ] 113 | } 114 | 115 | } 116 | , { 117 | "box" : { 118 | "maxclass" : "newobj", 119 | "text" : "r ajm.bring_to_front_callback", 120 | "fontname" : "Verdana", 121 | "numinlets" : 0, 122 | "patching_rect" : [ 110.0, 293.0, 177.0, 20.0 ], 123 | "fontsize" : 11.1, 124 | "numoutlets" : 1, 125 | "id" : "obj-23", 126 | "outlettype" : [ "" ] 127 | } 128 | 129 | } 130 | , { 131 | "box" : { 132 | "maxclass" : "newobj", 133 | "text" : "iter", 134 | "fontname" : "Verdana", 135 | "numinlets" : 1, 136 | "patching_rect" : [ 44.0, 79.0, 29.0, 20.0 ], 137 | "fontsize" : 11.1, 138 | "numoutlets" : 1, 139 | "id" : "obj-14", 140 | "outlettype" : [ "" ] 141 | } 142 | 143 | } 144 | , { 145 | "box" : { 146 | "maxclass" : "newobj", 147 | "text" : "patcherargs", 148 | "fontname" : "Verdana", 149 | "numinlets" : 1, 150 | "patching_rect" : [ 44.0, 53.0, 77.0, 20.0 ], 151 | "fontsize" : 11.1, 152 | "numoutlets" : 2, 153 | "id" : "obj-13", 154 | "outlettype" : [ "", "" ] 155 | } 156 | 157 | } 158 | , { 159 | "box" : { 160 | "maxclass" : "newobj", 161 | "text" : "prepend append", 162 | "fontname" : "Verdana", 163 | "numinlets" : 1, 164 | "patching_rect" : [ 21.0, 117.0, 101.0, 20.0 ], 165 | "fontsize" : 11.1, 166 | "numoutlets" : 1, 167 | "id" : "obj-4", 168 | "outlettype" : [ "" ] 169 | } 170 | 171 | } 172 | , { 173 | "box" : { 174 | "maxclass" : "inlet", 175 | "numinlets" : 0, 176 | "patching_rect" : [ 21.0, 11.0, 25.0, 25.0 ], 177 | "numoutlets" : 1, 178 | "id" : "obj-1", 179 | "outlettype" : [ "" ], 180 | "comment" : "" 181 | } 182 | 183 | } 184 | , { 185 | "box" : { 186 | "maxclass" : "comment", 187 | "varname" : "autohelp_see_title", 188 | "text" : "See Also:", 189 | "fontname" : "Arial", 190 | "presentation_rect" : [ 3.0, 4.0, 100.0, 20.0 ], 191 | "numinlets" : 1, 192 | "patching_rect" : [ 60.0, 16.0, 71.0, 20.0 ], 193 | "fontface" : 1, 194 | "fontsize" : 11.595187, 195 | "presentation" : 1, 196 | "numoutlets" : 0, 197 | "id" : "obj-8" 198 | } 199 | 200 | } 201 | , { 202 | "box" : { 203 | "maxclass" : "umenu", 204 | "varname" : "autohelp_see_menu", 205 | "fontname" : "Arial", 206 | "presentation_rect" : [ 5.0, 24.0, 130.0, 20.0 ], 207 | "items" : "(Objects)", 208 | "types" : [ ], 209 | "numinlets" : 1, 210 | "patching_rect" : [ 21.0, 153.0, 130.0, 20.0 ], 211 | "fontsize" : 11.595187, 212 | "presentation" : 1, 213 | "numoutlets" : 3, 214 | "id" : "obj-9", 215 | "outlettype" : [ "int", "", "" ] 216 | } 217 | 218 | } 219 | , { 220 | "box" : { 221 | "maxclass" : "panel", 222 | "varname" : "autohelp_see_panel", 223 | "presentation_rect" : [ 0.0, 0.0, 140.0, 50.0 ], 224 | "numinlets" : 1, 225 | "patching_rect" : [ 156.0, 19.0, 48.0, 42.0 ], 226 | "bordercolor" : [ 0.5, 0.5, 0.5, 0.75 ], 227 | "presentation" : 1, 228 | "border" : 2, 229 | "numoutlets" : 0, 230 | "id" : "obj-10", 231 | "bgcolor" : [ 0.85, 0.85, 0.85, 0.75 ], 232 | "background" : 1 233 | } 234 | 235 | } 236 | ], 237 | "lines" : [ { 238 | "patchline" : { 239 | "source" : [ "obj-19", 3 ], 240 | "destination" : [ "obj-21", 0 ], 241 | "hidden" : 0, 242 | "midpoints" : [ 127.0, 214.0, 347.0, 214.0, 347.0, 349.0, 119.5, 349.0 ] 243 | } 244 | 245 | } 246 | , { 247 | "patchline" : { 248 | "source" : [ "obj-19", 1 ], 249 | "destination" : [ "obj-15", 0 ], 250 | "hidden" : 0, 251 | "midpoints" : [ 100.0, 252.0, 113.5, 252.0 ] 252 | } 253 | 254 | } 255 | , { 256 | "patchline" : { 257 | "source" : [ "obj-19", 0 ], 258 | "destination" : [ "obj-21", 1 ], 259 | "hidden" : 0, 260 | "midpoints" : [ 87.5, 337.0 ] 261 | } 262 | 263 | } 264 | , { 265 | "patchline" : { 266 | "source" : [ "obj-9", 1 ], 267 | "destination" : [ "obj-19", 0 ], 268 | "hidden" : 0, 269 | "midpoints" : [ ] 270 | } 271 | 272 | } 273 | , { 274 | "patchline" : { 275 | "source" : [ "obj-27", 0 ], 276 | "destination" : [ "obj-15", 0 ], 277 | "hidden" : 0, 278 | "midpoints" : [ ] 279 | } 280 | 281 | } 282 | , { 283 | "patchline" : { 284 | "source" : [ "obj-19", 2 ], 285 | "destination" : [ "obj-27", 0 ], 286 | "hidden" : 0, 287 | "midpoints" : [ ] 288 | } 289 | 290 | } 291 | , { 292 | "patchline" : { 293 | "source" : [ "obj-6", 0 ], 294 | "destination" : [ "obj-22", 0 ], 295 | "hidden" : 0, 296 | "midpoints" : [ ] 297 | } 298 | 299 | } 300 | , { 301 | "patchline" : { 302 | "source" : [ "obj-23", 0 ], 303 | "destination" : [ "obj-20", 0 ], 304 | "hidden" : 0, 305 | "midpoints" : [ ] 306 | } 307 | 308 | } 309 | , { 310 | "patchline" : { 311 | "source" : [ "obj-20", 0 ], 312 | "destination" : [ "obj-21", 0 ], 313 | "hidden" : 0, 314 | "midpoints" : [ ] 315 | } 316 | 317 | } 318 | , { 319 | "patchline" : { 320 | "source" : [ "obj-21", 0 ], 321 | "destination" : [ "obj-6", 0 ], 322 | "hidden" : 0, 323 | "midpoints" : [ ] 324 | } 325 | 326 | } 327 | , { 328 | "patchline" : { 329 | "source" : [ "obj-14", 0 ], 330 | "destination" : [ "obj-4", 0 ], 331 | "hidden" : 0, 332 | "midpoints" : [ ] 333 | } 334 | 335 | } 336 | , { 337 | "patchline" : { 338 | "source" : [ "obj-13", 0 ], 339 | "destination" : [ "obj-14", 0 ], 340 | "hidden" : 0, 341 | "midpoints" : [ ] 342 | } 343 | 344 | } 345 | , { 346 | "patchline" : { 347 | "source" : [ "obj-1", 0 ], 348 | "destination" : [ "obj-4", 0 ], 349 | "hidden" : 0, 350 | "midpoints" : [ ] 351 | } 352 | 353 | } 354 | , { 355 | "patchline" : { 356 | "source" : [ "obj-4", 0 ], 357 | "destination" : [ "obj-9", 0 ], 358 | "hidden" : 0, 359 | "midpoints" : [ ] 360 | } 361 | 362 | } 363 | ] 364 | } 365 | 366 | } 367 | -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_argv.rb: -------------------------------------------------------------------------------- 1 | return if ARGV[0] == :mute 2 | puts ' ' 3 | puts 'These variables are set:' 4 | puts ' $0 = ' + $0.inspect 5 | puts ' __FILE__ = ' + __FILE__.inspect 6 | puts ' $* = ' + $*.inspect 7 | puts ' ARGV = ' + ARGV.inspect 8 | puts " $LOAD_PATH << #{$LOAD_PATH.last.inspect} # path to the @file is included on $LOAD_PATH" 9 | puts 'And these are other related values: ' 10 | puts ' File.basename($0) = ' + File.basename($0) 11 | puts ' File.dirname($0) = ' + File.dirname($0) 12 | -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_autowatch.rb: -------------------------------------------------------------------------------- 1 | # out0 "uncomment me and save this file" 2 | 3 | -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_call_send.rb: -------------------------------------------------------------------------------- 1 | def my_method(arg0,arg1,arg2) 2 | out0 "arg0=#{arg0}, arg1=#{arg1}, arg2=#{arg2}" 3 | end 4 | 5 | def another_method 6 | out0 "another method's return value" 7 | end 8 | 9 | class MyClass 10 | def method(*args) 11 | out0 'called out0 with:', *args 12 | end 13 | end 14 | 15 | @object = MyClass.new -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_file_example.rb: -------------------------------------------------------------------------------- 1 | def custom_method 2 | out0 "Hello from a custom method defined in a file" 3 | end 4 | 5 | def bang 6 | # out0 is a shortcut for: 7 | outlet(0, 'Hello from a custom handler for bang') 8 | end 9 | 10 | def inlet(inlet_index, *args) 11 | out0 'check the Max window' 12 | puts "received a list from inlet #{inlet_index} with #{args.length} items: ", args 13 | end -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_info_outlet.rb: -------------------------------------------------------------------------------- 1 | # try saving this file and watch for the 'fileloaded' message in the info outlet help patch -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_inlets.rb: -------------------------------------------------------------------------------- 1 | def in0(*params) 2 | out0 'sum', params.inject(:+) 3 | end 4 | 5 | # use of the splat (*params) is optionally 6 | # it's recommended because it makes the method more flexible 7 | def in2(param1, param2, param3, param4) 8 | params = [param1, param2, param3, param4] 9 | out0 'average', params.inject(:+).to_f/params.length 10 | end 11 | 12 | # when a convenience method (in1 in this case) is not 13 | # defined, it will call the inlet() method 14 | def inlet(inlet_index, *params) 15 | out0 'product', params.inject(:*) 16 | end -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_load_require.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.dirname($0) # put this folder on the LOAD_PATH 2 | -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_scriptfile.rb: -------------------------------------------------------------------------------- 1 | out0 'Hello from ajm_scriptfile.rb' 2 | -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_text_to.rb: -------------------------------------------------------------------------------- 1 | def inlet(inlet_index, *params) 2 | out0 *params 3 | end 4 | -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_variable_scope.rb: -------------------------------------------------------------------------------- 1 | ##################################################################### 2 | # ajm.ruby has separate local variable scopes for each evalution 3 | # If you need to remember a value across separate Max messages, you 4 | # need to use constants, globals, or attributes. 5 | # Note that a constant should only be defined once, and will result 6 | # in an error if you try to assign a different value later. 7 | 8 | Var = 'constants are remembered' 9 | $var = 'globals are remembered' 10 | @var = 'attributes are remembered' 11 | var = 'forgotten' 12 | 13 | def some_method 14 | 'methods are visible' 15 | end 16 | 17 | out0 "local variables are #{var} after a script's evaluation" 18 | -------------------------------------------------------------------------------- /jruby_for_max/lib/help/jruby_version.rb: -------------------------------------------------------------------------------- 1 | out0 JRUBY_VERSION, RUBY_VERSION -------------------------------------------------------------------------------- /jruby_for_max/lib/jruby_for_max/data_storage.rb: -------------------------------------------------------------------------------- 1 | module JRubyForMax 2 | 3 | # Methods for storage and retrieving data in various scopes when using multiple objects with shared @context 4 | module DataStorage 5 | 6 | ############################################################################# 7 | # local storage methods, for separate data per object while sharing context 8 | LOCAL_STORAGE = {} 9 | 10 | def set_local(name, obj) 11 | storage = LOCAL_STORAGE[$max_object] 12 | LOCAL_STORAGE[$max_object] = storage = {} if not storage 13 | storage[name] = obj 14 | end 15 | 16 | def get_local(name) 17 | storage = LOCAL_STORAGE[$max_object] 18 | storage[name] if storage 19 | end 20 | 21 | def delete_local(name) 22 | storage = LOCAL_STORAGE[$max_object] 23 | storage.delete(name) if storage 24 | end 25 | 26 | def has_local?(name) 27 | storage = LOCAL_STORAGE[$max_object] 28 | if storage 29 | storage.has_key?(name) 30 | else 31 | false 32 | end 33 | end 34 | 35 | module_function :set_local 36 | module_function :get_local 37 | module_function :delete_local 38 | module_function :has_local? 39 | 40 | 41 | ############################################################################# 42 | # global storage methods, for sharing data across contexts 43 | def set_global(name, obj) 44 | $global_variable_store[name.to_s] = obj 45 | end 46 | 47 | def get_global(name) 48 | $global_variable_store[name.to_s] 49 | end 50 | 51 | # get_global unless the data does not yet exist, in which case it will be set and then returned 52 | def init_global(name, initial_value) 53 | $global_variable_store[name.to_s] ||= initial_value 54 | end 55 | 56 | def delete_global(name) 57 | $global_variable_store.remove name.to_s 58 | end 59 | 60 | def has_global?(name) 61 | $global_variable_store.contains_key? name.to_s 62 | end 63 | 64 | module_function :set_global 65 | module_function :get_global 66 | module_function :init_global 67 | module_function :delete_global 68 | module_function :has_global? 69 | 70 | 71 | end 72 | 73 | end 74 | -------------------------------------------------------------------------------- /jruby_for_max/lib/jruby_for_max/delay.rb: -------------------------------------------------------------------------------- 1 | module JRubyForMax 2 | 3 | # A module for scheduling a callback to occur later, using the Max scheduler. 4 | # Most uses of this module can ignore the classes and just use the after() method. 5 | module Delay 6 | 7 | # Provides a simpler interface into com.cycling74.max.MaxClock 8 | class DelayedCallback 9 | attr_reader :clock, :executable 10 | def initialize(&callback) 11 | @executable = MaxExecutable.new(&callback) 12 | @clock = com.cycling74.max.MaxClock.new(@executable) 13 | end 14 | def execute_after(delay_in_ms) 15 | @clock.delay(delay_in_ms) 16 | at_exit { cancel } 17 | end 18 | def cancel 19 | @clock.release 20 | end 21 | end 22 | 23 | 24 | # JRuby implementation of com.cycling74.max.Executable 25 | class MaxExecutable 26 | include com.cycling74.max.Executable 27 | def initialize(&callback) 28 | @callback = callback 29 | end 30 | def execute() 31 | @callback.call() 32 | end 33 | end 34 | 35 | 36 | # Execute the callback block after the given delay in milliseconds. 37 | def after(delay_in_ms, &callback) 38 | delayed_callback = DelayedCallback.new(&callback) 39 | delayed_callback.execute_after delay_in_ms 40 | return delayed_callback # the callback is returned so it can be canceled 41 | end 42 | 43 | module_function :after 44 | 45 | end 46 | end -------------------------------------------------------------------------------- /jruby_for_max/lib/jruby_for_max/gem_manager.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems/commands/install_command' 2 | require 'rubygems/commands/uninstall_command' 3 | require 'rubygems/commands/update_command' 4 | 5 | module JRubyForMax 6 | 7 | # An object for listing, installing, updating, and removing gems in JRuby for Max. 8 | class GemManager 9 | class << self # All methods are class methods. 10 | 11 | # List all installed gems 12 | def list 13 | Gem::Specification.find_all 14 | end 15 | 16 | # Install the latest version of a gem, or a specific version if a version number is given 17 | def install gem_name, version=nil 18 | args = ['--no-ri', '--no-rdoc', gem_name] 19 | args += ['--version', version] if version 20 | cmd = Gem::Commands::InstallCommand.new 21 | cmd.handle_options args 22 | cmd.execute 23 | rescue Gem::SystemExitException => e 24 | # Not sure why the install command raises SystemExitException in successful cases, but it does. 25 | # So we just check for a success exit code 0. 26 | raise unless e.exit_code == 0 27 | end 28 | 29 | # Uninstall all versions of a gem, or a specific version if a version number is given 30 | def uninstall gem_name, version=nil 31 | cmd = Gem::Commands::UninstallCommand.new 32 | args = ['-x', '-I', gem_name] # -x removes executables without prompting. -I ignores dependecies. 33 | args += ['--version', version] if version 34 | cmd.handle_options args 35 | cmd.execute 36 | end 37 | 38 | # Update a gem to the latest version. 39 | def update gem_name 40 | cmd = Gem::Commands::UpdateCommand.new 41 | cmd.handle_options ['--no-rdoc', '--no-ri', gem_name] 42 | cmd.execute 43 | end 44 | 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /jruby_for_max/lib/jruby_for_max/live_api.rb: -------------------------------------------------------------------------------- 1 | # A Ruby interface into the Live API 2 | module JRubyForMax::LiveAPI 3 | end 4 | 5 | require 'jruby_for_max/live_api/live_object' 6 | require 'jruby_for_max/live_api/note' 7 | require 'jruby_for_max/live_api/clip' 8 | 9 | -------------------------------------------------------------------------------- /jruby_for_max/lib/jruby_for_max/live_api/clip.rb: -------------------------------------------------------------------------------- 1 | module JRubyForMax::LiveAPI 2 | 3 | # A represetation of a Clip 4 | class Clip < LiveObject 5 | 6 | attr_reader :notes 7 | 8 | def goto_selected_clip &block 9 | goto :live_set, :view, :highlighted_clip_slot, :clip, &block 10 | end 11 | 12 | def goto_clip track_index, scene_index, &block 13 | goto :live_set, :tracks, track_index, :clip_slots, scene_index, :clip, &block 14 | end 15 | 16 | def get_notes &block 17 | return if not exists? 18 | # along with the notes, we'll need the clip length and loop_start point to 19 | # be able to figure out where in the clips the notes are, so we get those first: 20 | get :loop_start do 21 | get :loop_end do 22 | call :select_all_notes do 23 | call :get_selected_notes do |*data| 24 | case data.shift # first data element is note property 25 | when :notes 26 | @notes = [] 27 | when :note 28 | @notes << Note.new(*data) 29 | when :done 30 | block.call(@notes) if block 31 | end 32 | end 33 | end 34 | end 35 | end 36 | end 37 | 38 | def set_notes(notes=nil) 39 | notes ||= @notes 40 | call :replace_selected_notes 41 | call :notes, notes.size 42 | notes.each { |note| call :note, *note.to_message } 43 | call :done 44 | end 45 | 46 | def transform_notes &block 47 | return if not block 48 | get_notes do 49 | if not @notes.empty? 50 | block.call(@notes) 51 | set_notes 52 | end 53 | end 54 | end 55 | 56 | def transform_selected_clip &block 57 | goto_selected_clip do 58 | transform_notes &block 59 | end 60 | end 61 | 62 | def transform_clip track_index, scene_index, &block 63 | goto_clip track_index, scene_index do 64 | transform_notes &block 65 | end 66 | end 67 | 68 | def length 69 | @loop_end - @loop_start 70 | end 71 | 72 | end 73 | 74 | end 75 | 76 | -------------------------------------------------------------------------------- /jruby_for_max/lib/jruby_for_max/live_api/live_object.rb: -------------------------------------------------------------------------------- 1 | module JRubyForMax::LiveAPI 2 | 3 | # A generic representation of a Live object 4 | class LiveObject 5 | 6 | attr_reader :object_path 7 | 8 | def initialize options={} 9 | @live_object_outlet = options.fetch :live_object_outlet, 0 10 | @live_path_outlet = options.fetch :live_path_outlet, 1 11 | @callbacks = {} 12 | end 13 | 14 | def exists? 15 | not @live_id.nil? and not @live_id == 0 16 | end 17 | 18 | def object_not_found &block 19 | @object_not_found_callback = block 20 | end 21 | 22 | def goto *object_path, &block 23 | @object_path = object_path 24 | attribute = init_variable :live_id 25 | @callbacks[attribute] = block 26 | # given input like :a, :b, :c 27 | # output 'goto', 'a', 'b', 'c' 28 | outlet @live_path_outlet, object_path.map { |subpath| subpath.to_s }.unshift('goto') 29 | end 30 | 31 | def call function, *args, &block 32 | @callbacks[function] = block 33 | outlet @live_object_outlet, 'call', function.to_s, *args 34 | end 35 | 36 | def get property, &block 37 | attribute = init_variable property 38 | @callbacks[attribute] = block 39 | outlet @live_object_outlet, 'get', property.to_s 40 | end 41 | 42 | # Handler for the results of both call() and get(). 43 | def set live_property, *data 44 | #puts "set #{live_property} = #{data.inspect}" 45 | attribute, variable = attribute_for live_property 46 | value = data.first 47 | 48 | # We rely on get() initializing an instance variable to determine 49 | # if we're actually setting a property or just handling the results of a call() 50 | if instance_variables.include? variable.to_sym 51 | instance_variable_set variable, value 52 | end 53 | 54 | if attribute == :live_id and not exists? 55 | @object_not_found_callback.call(@object_path) if @object_not_found_callback 56 | else 57 | callback = @callbacks[attribute] 58 | callback.call(*data) if callback 59 | end 60 | end 61 | 62 | ############################ 63 | private 64 | 65 | def init_variable live_property 66 | attribute, variable = attribute_for live_property 67 | if not instance_variables.include? variable 68 | # if we're defining a property for the first time, create an accessor method 69 | self.class.instance_eval do 70 | attr_reader attribute 71 | end 72 | end 73 | instance_variable_set variable, nil 74 | return attribute 75 | end 76 | 77 | # Get the Ruby attribute name and instance variable name for a live_property 78 | def attribute_for live_property 79 | live_property = live_property.to_sym 80 | attribute = 81 | case live_property 82 | when :id 83 | :live_id # avoid conflicts with Ruby's Object#id() 84 | else 85 | live_property 86 | end 87 | return attribute, "@#{attribute}" 88 | end 89 | end 90 | 91 | end 92 | 93 | -------------------------------------------------------------------------------- /jruby_for_max/lib/jruby_for_max/live_api/note.rb: -------------------------------------------------------------------------------- 1 | module JRubyForMax::LiveAPI 2 | 3 | class Note 4 | attr_accessor :pitch, :start, :duration, :velocity, :deactivated 5 | 6 | def initialize(*live_api_data) 7 | @pitch, @start, @duration, @velocity, @deactivated = *live_api_data 8 | end 9 | 10 | def active? 11 | @deactivated == 0 12 | end 13 | 14 | def length 15 | @duration 16 | end 17 | 18 | def to_message 19 | [@pitch, @start, @duration, @velocity, @deactivated] 20 | end 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /jruby_for_max/lib/jruby_for_max/send_receive.rb: -------------------------------------------------------------------------------- 1 | module JRubyForMax 2 | 3 | # Methods for communicating between [mxj jruby] objects 4 | # _send_ a message to any objects that have registered to _receive_ that message 5 | module SendReceive 6 | 7 | # The list of all receivers registered to receive a message 8 | def self.registry 9 | @registry ||= init_global('JRubyForMax::SendReceive.registry', 10 | # using the global variable store so any jruby instances can communicate 11 | Hash.new { |hash, key| hash[key] = [] } # initialize missing entries to an empty list 12 | ) 13 | end 14 | 15 | 16 | # Registers to receive a message by way of a callback 17 | def receive(message, &callback) 18 | lock = $_LOCK_ # get a reference to the receiver's lock 19 | # and use it to synchronize the callback with any other input that may be happening at that receiver 20 | synchronized_callback = lambda do |*args| 21 | lock.synchronize do 22 | callback.call(*args) 23 | end 24 | end 25 | # It seems that if we are going to use the global variable store, then the key (the message) 26 | # needs to be converted to a String (identical symbols are apparently not equal across Ruby evaluators.) 27 | SendReceive.registry[message.to_s] << [synchronized_callback, $max_object] # and records the binding for the current $max_object. 28 | end 29 | 30 | module_function :receive 31 | 32 | # Unregister to receive a message. 33 | # If no message is provided, then unregister all receivers for this max object. 34 | def unreceive(message=nil) 35 | for msg, receivers in SendReceive.registry 36 | next if message and message.to_s != msg 37 | receivers.delete_if { |_, max_object| max_object == $max_object } # $max_object is the current max object 38 | end 39 | end 40 | 41 | module_function :unreceive 42 | 43 | 44 | # Notify all receivers of a message and any additional arguments. 45 | def send(message, *args) 46 | # Now we have to swap some variable references around in order to restore the binding 47 | # for $max_object that existed at the time the receive callback was registered. 48 | # This ensures the outlet methods work correctly. 49 | current_max_object = $max_object 50 | for callback, $max_object in SendReceive.registry[message.to_s] do 51 | begin 52 | # $max_object.getNumOutlets # this blows up after deleting receivers in a shared context 53 | callback.call(*args) 54 | rescue 55 | 56 | end 57 | end 58 | ensure 59 | $max_object = current_max_object 60 | end 61 | 62 | module_function :send 63 | 64 | 65 | at_exit do 66 | unreceive # cleanup max_objects which were deleted 67 | end 68 | 69 | 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /jruby_for_max/lib/jruby_initialize.rb: -------------------------------------------------------------------------------- 1 | require 'java' 2 | require 'jruby_for_max/data_storage' 3 | include JRubyForMax::DataStorage 4 | require 'thread' 5 | $_LOCK_ = Mutex.new # used to synchronize evaluation across multiple threads (with poly~ or Max for Live) 6 | 7 | # 8 | # Core interface for JRuby for Max 9 | # 10 | # The code in this file is available to every instance of [mxj jruby] 11 | # Think of this file as automatically required by your script 12 | 13 | ############################################################################### 14 | # predefined GLOBAL VARIABLES 15 | # 16 | # $max_object : the current [mxj jruby] Max object 17 | # $max_object_map : lookup table {context => {id => max_object}} which contains all [mxj jruby] objects in Max 18 | # $global_variable_store : a singleton Map stored at the JVM level and shared across all instances 19 | # $max_ruby_adapter : the bridge between the max object and ruby, see the Java class MaxRubyAdapter 20 | 21 | ############################################################################### 22 | # Interface to the containing patcher 23 | 24 | # Handler for input received at an inlet 25 | # Intended to be overridden. Default implementation passes the input through to the output 26 | def inlet(inlet_index, *params) 27 | outlet inlet_index, *params if inlet_index < $max_object.numOutlets 28 | return *params 29 | end 30 | 31 | # Sends output to an outlet 32 | def outlet(outlet_index, *params) 33 | if (outlet_index >= $max_object.numOutlets) 34 | error("Invalid outlet index #{outlet_index}") 35 | else 36 | begin 37 | if params.length == 1 38 | # avoid unnecessary nested arrays for things like "outlet 0, [1,2]" 39 | atoms = $max_ruby_adapter.toAtoms(params[0]) 40 | else 41 | atoms = $max_ruby_adapter.toAtoms(params) 42 | end 43 | rescue # this compensates for http://jira.codehaus.org/browse/JRUBY-4998 44 | if params.length == 1 45 | atoms = $max_ruby_adapter.toAtoms(params[0].to_s) 46 | else 47 | atoms = $max_ruby_adapter.toAtoms( params.map{|p| p.to_s} ) 48 | end 49 | end 50 | $max_object.outlet(outlet_index, atoms) 51 | end 52 | nil 53 | end 54 | 55 | # 10 convenience methods for inlet and outlet 56 | module Kernel 57 | 10.times do |index| 58 | 59 | # in0, in1, ..., in9 handlers are called when input is received in one of the first 10 inlets 60 | # These are intended to be overridden. Default implementation calls inlet() 61 | define_method "in#{index}" do |*params| 62 | inlet(index, *params) 63 | end 64 | 65 | # out0, out1, ..., out9 methods send output to one of the first 10 inlets 66 | define_method "out#{index}" do |*params| 67 | outlet(index, *params) 68 | end 69 | 70 | end 71 | end 72 | 73 | # The index of the inlet that most recently received input 74 | def inlet_index 75 | $max_object.getInlet 76 | end 77 | 78 | # The handler for bang messages received at any inlet 79 | # This is intended to be overridden. Default implementation calls in0()-in9() or inlet() with the argument :bang 80 | def bang 81 | if inlet_index <= 9 82 | # prefer the short form: 83 | eval "in#{inlet_index}( 'bang' )" 84 | else 85 | inlet( inlet_index, 'bang' ) 86 | end 87 | 'bang' 88 | end 89 | 90 | # Sets the tooltips for the inlets of this [mxj jruby] object 91 | def inlet_assist(*params) 92 | $max_object.setInletAssist params.to_java(:string) 93 | end 94 | 95 | # Sets the tooltips for the outlets of this [mxj jruby] object 96 | def outlet_assist(*params) 97 | $max_object.setOutletAssist params.to_java(:string) 98 | end 99 | 100 | 101 | ############################################################################### 102 | # Object registries and storage 103 | 104 | # get the current max_object or lookup one by namespace 105 | def max_object(namespace=nil) 106 | return $max_object if not namespace 107 | names = namespace.split '.' 108 | if(names.length > 1) 109 | context = names[0] 110 | id = names[1] 111 | else 112 | context = $max_object.context 113 | id = names[0] 114 | end 115 | $max_object_map[context][id] 116 | end 117 | 118 | 119 | ############################################################################### 120 | # Interface to Max console 121 | 122 | # Print the arguments to the Max console separated by newlines 123 | def puts(*params) 124 | yield_atoms(*params) {|atom| java.lang.System.out.println(atom)} 125 | nil 126 | end 127 | 128 | # Prints the arguments to the Max console 129 | def print(*params) 130 | yield_atoms(*params) {|atom| java.lang.System.out.print(atom)} 131 | nil 132 | end 133 | 134 | # Prints an error message to the Max console 135 | def error(*params) 136 | yield_atoms(*params) {|atom| java.lang.System.err.println(atom)} 137 | nil 138 | end 139 | 140 | # Flush the Max console, use after print() 141 | def flush 142 | java.lang.System.out.println 143 | nil 144 | end 145 | 146 | 147 | ############################################################################### 148 | # Datatype conversion 149 | 150 | # Converts a Ruby object to a Max Atom (the core datatype in Max) 151 | def atom( obj=nil ) 152 | if obj 153 | $max_ruby_adapter.toAtoms(obj) 154 | else 155 | com.cycling74.max.Atom.emptyArray 156 | end 157 | end 158 | 159 | # Converts a list of parameters to a list of Max Atoms and yields those Atoms to the block 160 | def yield_atoms( *params, &block ) 161 | params.each do |param| 162 | begin 163 | atoms_or_atom = $max_ruby_adapter.toAtoms(param) 164 | rescue # this compensates for http://jira.codehaus.org/browse/JRUBY-4998 165 | atoms_or_atom = param.to_s 166 | end 167 | if atoms_or_atom.respond_to? :each 168 | atoms_or_atom.each{|atom| yield atom} 169 | else 170 | yield atoms_or_atom 171 | end 172 | end 173 | end 174 | 175 | -------------------------------------------------------------------------------- /jruby_for_max/lib/support/gem_manager_interface.rb: -------------------------------------------------------------------------------- 1 | require 'jruby_for_max/gem_manager' 2 | Gems = JRubyForMax::GemManager 3 | 4 | def env 5 | out0 'env', 'GEM_HOME', ENV['GEM_HOME'] 6 | out0 'env', 'GEM_PATH', ENV['GEM_PATH'] 7 | end 8 | 9 | def list 10 | gems_by_name = Hash.new{|h,k| h[k] = []} 11 | Gems.list.each{|gem| gems_by_name[gem.name] << gem.version.to_s } 12 | 13 | out0 'gems', gems_by_name.keys.size 14 | gems_by_name.each do |name, versions| 15 | out0 'gem', name, versions.join(', ') 16 | end 17 | out0 'done' 18 | end 19 | 20 | def install *args 21 | manage_gem_in_background :install, *args 22 | end 23 | 24 | def uninstall *args 25 | manage_gem_in_background :uninstall, *args 26 | end 27 | 28 | def manage_gem_in_background operation, name, version 29 | if name=='_NO_GEM_NAME_' 30 | out1 'status', 'ERROR: enter a gem name' 31 | return 32 | end 33 | if version=='_NO_GEM_VERSION_' 34 | version = nil 35 | end 36 | 37 | if @bg_thread 38 | error "Ignored attempt to #{operation} a gem, because another gem is currently being installed or uninstalled." 39 | return 40 | end 41 | 42 | @bg_thread = Thread.new do 43 | out1 'status', "#{operation}ing #{name} #{version} ..." 44 | out1 'spinner', 1 45 | begin 46 | Gems.send operation, name, version 47 | out1 'status', '' 48 | rescue Exception 49 | out1 'status', "ERROR: see Max console for details" 50 | ensure 51 | list 52 | out1 'spinner', 0 53 | @bg_thread = nil 54 | end 55 | end 56 | end -------------------------------------------------------------------------------- /lib/ant-junit.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/jruby_for_max/8f3d3074ad17cd647e14efcb455a9931033b8507/lib/ant-junit.jar -------------------------------------------------------------------------------- /lib/jruby.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/jruby_for_max/8f3d3074ad17cd647e14efcb455a9931033b8507/lib/jruby.jar -------------------------------------------------------------------------------- /lib/junit-4.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/jruby_for_max/8f3d3074ad17cd647e14efcb455a9931033b8507/lib/junit-4.5.jar -------------------------------------------------------------------------------- /lib/max.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamjmurray/jruby_for_max/8f3d3074ad17cd647e14efcb455a9931033b8507/lib/max.jar -------------------------------------------------------------------------------- /license/LICENSE-jruby_for_max.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011, Adam Murray (adam@compusition.com). 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use of JRuby for Max in source and binary forms, with or 6 | without modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in 14 | the documentation and/or other materials provided with the 15 | distribution. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 21 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 25 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 26 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /license/jruby/COPYING: -------------------------------------------------------------------------------- 1 | JRuby is released under a tri CPL/GPL/LGPL license. You can use it, 2 | redistribute it and/or modify it under the terms of the: 3 | 4 | CPL - see COPYING.CPL file 5 | GPL - see COPYING.GPL file 6 | LGPL - see COPYING.LGPL file 7 | 8 | Some additional libraries distributed with JRuby are not covered by 9 | JRuby's licence. See the licence files for the respective libraries in 10 | the 'lib' directory for more information and also LICENSE.RUBY for most 11 | files found in src/lib/ruby/1.8. 12 | -------------------------------------------------------------------------------- /license/jruby/COPYING.CPL: -------------------------------------------------------------------------------- 1 | Common Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and 10 | b) in the case of each subsequent Contributor: 11 | 12 | i) changes to the Program, and 13 | 14 | ii) additions to the Program; 15 | 16 | where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. 17 | 18 | "Contributor" means any person or entity that distributes the Program. 19 | 20 | "Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 21 | 22 | "Program" means the Contributions distributed in accordance with this Agreement. 23 | 24 | "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 25 | 26 | 2. GRANT OF RIGHTS 27 | 28 | a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. 29 | 30 | b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 31 | 32 | c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 33 | 34 | d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 35 | 36 | 3. REQUIREMENTS 37 | 38 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 39 | 40 | a) it complies with the terms and conditions of this Agreement; and 41 | 42 | b) its license agreement: 43 | 44 | i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 45 | 46 | ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 47 | 48 | iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and 49 | 50 | iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. 51 | 52 | When the Program is made available in source code form: 53 | 54 | a) it must be made available under this Agreement; and 55 | 56 | b) a copy of this Agreement must be included with each copy of the Program. 57 | 58 | Contributors may not remove or alter any copyright notices contained within the Program. 59 | 60 | Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 61 | 62 | 4. COMMERCIAL DISTRIBUTION 63 | 64 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 65 | 66 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 67 | 68 | 5. NO WARRANTY 69 | 70 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 71 | 72 | 6. DISCLAIMER OF LIABILITY 73 | 74 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 75 | 76 | 7. GENERAL 77 | 78 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 79 | 80 | If Recipient institutes patent litigation against a Contributor with respect to a patent applicable to software (including a cross-claim or counterclaim in a lawsuit), then any patent licenses granted by that Contributor to such Recipient under this Agreement shall terminate as of the date such litigation is filed. In addition, if Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 81 | 82 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 83 | 84 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. IBM is the initial Agreement Steward. IBM may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. 85 | 86 | This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. 87 | -------------------------------------------------------------------------------- /license/jruby/COPYING.GPL: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Library General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS -------------------------------------------------------------------------------- /src/jruby.java: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2011, Adam Murray (adam@compusition.com). All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 18 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | */ 27 | 28 | import com.cycling74.max.Atom; 29 | import com.cycling74.max.Executable; 30 | import com.cycling74.max.MaxPatcher; 31 | import com.cycling74.max.MaxSystem; 32 | import jruby4max.maxsupport.JRubyMaxObject; 33 | import jruby4max.rubysupport.InputConversionOption; 34 | import jruby4max.util.FileWatcher; 35 | import jruby4max.util.TextBlock; 36 | import jruby4max.util.Utils; 37 | 38 | import java.io.File; 39 | import java.io.IOException; 40 | import java.util.ArrayList; 41 | import java.util.Date; 42 | import java.util.List; 43 | 44 | /** 45 | * The jruby4max.ruby MaxObject 46 | * 47 | * @author Adam Murray (adam@compusition.com) 48 | */ 49 | public class jruby extends JRubyMaxObject { 50 | 51 | private String filePath; 52 | private File file; 53 | private Atom[] fileArgs; 54 | 55 | // Any info outlet messages that need to be sent immediately after initialization, because 56 | // outlet calls during initialization will be ignored 57 | private String[] infoOutletMessageOnInit; 58 | 59 | private boolean hasInfoOutlet = false; 60 | private int evalOutlet = -1; 61 | 62 | private boolean autowatch = false; 63 | private FileWatcher fileWatcher; 64 | 65 | private String[] textTo; 66 | private InputConversionOption[] inputConversionOptions; 67 | 68 | private boolean synchronize = false; 69 | 70 | private static final String DEFAULT_EXTENSION = ".rb"; 71 | 72 | // Used to buffer any input that needs to be evaluated after initialization completes 73 | private List deferredEvals; 74 | 75 | /** 76 | * The Constructor 77 | * 78 | * @param args 1 optional integer arg specifies the number of outlets 79 | */ 80 | public jruby( Atom[] args ) { 81 | super(); 82 | declareAttribute( "evalout", "getevalout", "evalout" ); 83 | declareAttribute( "file", "getfile", "file" ); 84 | declareAttribute( "autowatch", "getautowatch", "autowatch" ); 85 | 86 | declareAttribute( "text_to", "gettext_to", "text_to" ); 87 | declareAttribute( "symbols_to", "gettext_to", "text_to" ); // deprecated, use @text_to instead 88 | 89 | declareAttribute( "synchronize" ); 90 | 91 | int inlets = 1; 92 | if( args.length > 0 && args[0].isInt() && args[0].getInt() > 1 ) { 93 | inlets = args[0].getInt(); 94 | } 95 | int outlets = 1; 96 | if( args.length > 1 && args[1].isInt() && args[1].getInt() > 1 ) { 97 | outlets = args[1].getInt(); 98 | } 99 | 100 | if( args.length > 2 && (args[2].isInt() || args[2].isString()) ) { 101 | String infoOutletArg = args[2].toString().toLowerCase(); 102 | if( infoOutletArg.equals( "1" ) || infoOutletArg.equals( "true" ) ) { 103 | hasInfoOutlet = true; 104 | } 105 | } 106 | declareIO( inlets, outlets ); 107 | createInfoOutlet( hasInfoOutlet ); 108 | 109 | inputConversionOptions = new InputConversionOption[inlets]; 110 | for( int i = 0; i < inputConversionOptions.length; i++ ) { 111 | // the default behavior is to treat input literally 112 | inputConversionOptions[i] = InputConversionOption.DEFAULT; 113 | } 114 | } 115 | 116 | @Override 117 | protected Executable getInitializer() { 118 | return new RubyInitializer(); 119 | } 120 | 121 | protected class RubyInitializer extends DefaultRubyInitializer { 122 | @Override 123 | public void execute() { 124 | super.execute(); 125 | if( file != null ) { 126 | initFile(); 127 | } 128 | if( hasInfoOutlet ) { 129 | if( infoOutletMessageOnInit != null ) { 130 | outlet( getInfoIdx(), infoOutletMessageOnInit ); 131 | infoOutletMessageOnInit = null; 132 | } 133 | outlet( getInfoIdx(), "initialized", "bang" ); 134 | } 135 | if( deferredEvals != null) { 136 | for(CharSequence input : deferredEvals ) { 137 | evalRuby( input ); 138 | } 139 | deferredEvals = null; 140 | } 141 | } 142 | } 143 | 144 | @Override 145 | public void notifyDeleted() { 146 | stopFileWatcher(); 147 | super.notifyDeleted(); 148 | } 149 | 150 | public int getevalout() { 151 | return evalOutlet; 152 | } 153 | 154 | public void evalout( int evalout ) { 155 | if( evalout >= getNumOutlets() ) { 156 | err( "Invalid evalout " + evalout ); 157 | } 158 | else { 159 | this.evalOutlet = evalout; 160 | } 161 | } 162 | 163 | public String getfile() { 164 | if( file != null ) { 165 | return file.getAbsolutePath(); 166 | } 167 | else { 168 | return null; 169 | } 170 | } 171 | 172 | public void file( Atom[] args ) { 173 | if( args != null && args.length > 0 ) { 174 | filePath = args[0].toString(); 175 | file = Utils.getFile( filePath, getParentPatcher(), true, DEFAULT_EXTENSION ); 176 | fileArgs = Atom.removeFirst( args ); 177 | if( file == null ) { 178 | err( "File not found: " + filePath ); 179 | if( Utils.isPatcherSaved( getParentPatcher() ) ) { 180 | info( "Send the \"createfile\" message to create the file \"" + getFileToBeCreated() + "\"" ); 181 | } 182 | else { 183 | info( "Save the patcher and send the \"createfile\" message" ); 184 | info( "...to create the file \"" + filePath + "\" in the patcher's folder." ); 185 | } 186 | 187 | if( hasInfoOutlet ) { 188 | if( initialized ) { 189 | outlet( getInfoIdx(), "filenotfound", filePath ); 190 | } 191 | else { 192 | infoOutletMessageOnInit = new String[]{"filenotfound", filePath}; 193 | } 194 | } 195 | } 196 | } 197 | else { 198 | filePath = null; 199 | file = Utils.getFile( null ); 200 | fileArgs = Atom.emptyArray; 201 | } 202 | 203 | if( file != null && initialized ) { 204 | initFile(); 205 | } // if not initialized initFile() will be called by the Initializer 206 | } 207 | 208 | public boolean getautowatch() { 209 | return autowatch; 210 | } 211 | 212 | public void autowatch( boolean autowatch ) { 213 | this.autowatch = autowatch; 214 | if( initialized ) { 215 | if( autowatch ) { 216 | startFileWatcher(); 217 | } 218 | else { 219 | stopFileWatcher(); 220 | } 221 | } 222 | } 223 | 224 | public String[] gettext_to() { 225 | return textTo; 226 | } 227 | 228 | public void text_to( String[] params ) { 229 | for( int i = 0; i < params.length; i++ ) { 230 | String param = params[i]; 231 | 232 | if( i >= inputConversionOptions.length ) { 233 | err( "More @text_to arguments than inlets, ignoring extra arguments." ); 234 | break; 235 | } 236 | 237 | InputConversionOption option = InputConversionOption.fromString( param ); 238 | if( option == null ) { 239 | err( "Invalid @text_to argument '" + param + "'. Defaulting to " + InputConversionOption.DEFAULT ); 240 | // it was already initialized to the default in the constructor so there's nothing to do 241 | continue; 242 | } 243 | else if( option == InputConversionOption.REMAINING_INLETS ) { 244 | if( i > 0 ) { 245 | InputConversionOption previousOption = inputConversionOptions[i - 1]; 246 | for( int j = i; j < inputConversionOptions.length; j++ ) { 247 | inputConversionOptions[j] = previousOption; 248 | } 249 | } 250 | // else the first arg is a '*' in which case there's nothing to do (everything remains the default) 251 | 252 | if( i < params.length - 1 ) { 253 | err( "Ignoring @symbols_to argument(s) after the " + InputConversionOption.REMAINING_INLETS ); 254 | } 255 | // and we're done: 256 | break; 257 | } 258 | else { 259 | // handle all non-REMAINING_INLETS options: 260 | inputConversionOptions[i] = option; 261 | } 262 | } 263 | textTo = new String[inputConversionOptions.length]; 264 | for( int i = 0; i < textTo.length; i++ ) { 265 | textTo[i] = inputConversionOptions[i].toString(); 266 | } 267 | } 268 | 269 | private void startFileWatcher() { 270 | if( fileWatcher == null ) { 271 | if( file != null && autowatch ) { 272 | fileWatcher = new FileWatcher( file, fileWatcherCallback ); 273 | fileWatcher.start(); 274 | } 275 | } 276 | else { 277 | fileWatcher.setFile( file ); // in case a new file was loaded - see scriptfile() / initFile() 278 | fileWatcher.resumeWatching(); 279 | } 280 | } 281 | 282 | private void stopFileWatcher() { 283 | if( fileWatcher != null ) { 284 | fileWatcher.pauseWatching(); 285 | } 286 | } 287 | 288 | private Executable fileWatcherCallback = new Executable() { 289 | public void execute() { 290 | loadFile(); 291 | } 292 | }; 293 | 294 | private void initFile() { 295 | stopFileWatcher(); 296 | loadFile(); 297 | startFileWatcher(); 298 | } 299 | 300 | private synchronized void loadFile() { 301 | info( "loading script '" + file + "' on " + new Date() ); 302 | try { 303 | ruby.init( file, fileArgs ); 304 | if( hasInfoOutlet ) { 305 | outlet( getInfoIdx(), "fileloaded", "bang" ); 306 | } 307 | } catch( Exception e ) { 308 | err( "Error evaluating script file: " + file.getPath() ); 309 | if( hasInfoOutlet ) { 310 | outlet( getInfoIdx(), "error", e.getMessage() ); 311 | } 312 | // It seems that System.err.flush() takes care of printing out the error message. 313 | // if(verbose) { 314 | // printRubyException(e); 315 | // } 316 | } 317 | flush(); 318 | } 319 | 320 | public void bang() { 321 | evalRuby( "bang()" ); 322 | } 323 | 324 | public void list( Atom[] args ) { 325 | StringBuilder s = new StringBuilder(); 326 | int inletIdx = getInlet(); 327 | if( inletIdx <= 9 ) { 328 | s.append( "in" ).append( inletIdx ).append( "(" ); 329 | } 330 | else { 331 | s.append( "inlet(" ).append( inletIdx ).append( "," ); 332 | } 333 | joinArgs( args, s, 0 ); 334 | s.append( ")" ); 335 | evalRuby( s ); 336 | } 337 | 338 | public void symbol( Atom[] args ) { 339 | // a convenience for scenarios like attaching a 'coll' directly to this object 340 | list( args ); 341 | } 342 | 343 | /** 344 | * @param args - method methods_args 345 | */ 346 | public void call( Atom[] args ) { 347 | if( args.length < 1 || args[0] == null ) return; 348 | StringBuilder s = new StringBuilder(); 349 | s.append( args[0] ); 350 | s.append( "(" ); 351 | joinArgs( args, s, 1 ); 352 | s.append( ")" ); 353 | evalRuby( s ); 354 | } 355 | 356 | /** 357 | * @param args - receiver method methods_args 358 | */ 359 | public void send( Atom[] args ) { 360 | if( args.length < 2 || args[0] == null || args[1] == null ) return; 361 | StringBuilder s = new StringBuilder(); 362 | s.append( args[0] ).append( "." ).append( args[1] ); 363 | s.append( "(" ); 364 | joinArgs( args, s, 2 ); 365 | s.append( ")" ); 366 | evalRuby( s ); 367 | } 368 | 369 | public void eval( Atom[] args ) { 370 | StringBuilder input = new StringBuilder(); 371 | for( Atom arg : args ) { 372 | input.append( Utils.detokenize( arg.toString() ) ).append( " " ); 373 | } 374 | evalRuby( input.toString().trim() ); 375 | } 376 | 377 | private void joinArgs( Atom[] args, StringBuilder s, int startIndex ) { 378 | for( int i = startIndex; i < args.length; i++ ) { 379 | if( i > startIndex ) { 380 | s.append( "," ); 381 | } 382 | Atom arg = args[i]; 383 | if( arg == null ) { 384 | continue; 385 | } 386 | String value = arg.toString(); 387 | 388 | if( arg.isString() ) { 389 | switch( inputConversionOptions[getInlet()] ) { 390 | case STRING: 391 | if( value.startsWith( "\"" ) && value.endsWith( "\"" ) ) { 392 | value = value.substring( 1, value.length()-1 ); 393 | } 394 | value = "'" + value.replaceAll( "'", "\\\\'" ) + "'"; 395 | break; 396 | 397 | case INTERPOLATED: 398 | if( !(value.startsWith( "\"" ) && value.endsWith( "\"" )) ) { 399 | // This might not work well with evaluated strings #{} if the double quotes is part of the evaluated expression. 400 | // In that case a workaround could be to try to refer to the quote with the unicode escape code? 401 | value = '"' + value.replaceAll( "\"", "\\\\\"" ) + '"'; 402 | } 403 | break; 404 | 405 | case SYMBOL: 406 | value = ':' + value.replaceAll( " ", "_" ); 407 | break; 408 | 409 | default: 410 | value = Utils.detokenize( value ); 411 | } 412 | } 413 | 414 | s.append( value ); 415 | } 416 | } 417 | 418 | public void text( String script ) { 419 | evalRuby( script.trim() ); 420 | } 421 | 422 | public void textblock( String name ) { 423 | String script = TextBlock.get( name ); 424 | if( script != null ) { 425 | evalRuby( script ); 426 | } 427 | else { 428 | error( "No textblock named '" + name + "'" ); 429 | } 430 | } 431 | 432 | protected void evalRuby( CharSequence input ) { 433 | try { 434 | if( ruby == null ) { 435 | if( deferredEvals == null) deferredEvals = new ArrayList(); 436 | deferredEvals.add( input ); 437 | // err( "not initialized yet. Did not evaluate: " + input 438 | // + ". If you are loadbanging a script, try using the 'info outlet', or a deferlow object." ); 439 | return; 440 | } 441 | CharSequence code = input; 442 | if( synchronize ) { 443 | // automatically synchronize all evaluations: 444 | code = "$_LOCK_.synchronize do\n" + input + "\nend"; 445 | } 446 | 447 | Object val = ruby.eval( code, evalOutlet >= 0 ); 448 | if( evalOutlet >= 0 ) { 449 | if( val instanceof Atom[] ) { 450 | outlet( evalOutlet, (Atom[]) val ); 451 | } 452 | else { 453 | outlet( evalOutlet, (Atom) val ); 454 | } 455 | } 456 | // else negative numbers to suppress eval output 457 | 458 | } catch( Exception e ) { 459 | err( "could not evaluate: " + input ); 460 | if( hasInfoOutlet ) { 461 | outlet( getInfoIdx(), "error", e.getMessage() ); 462 | } 463 | // It seems that System.err.flush() takes care of printing out the error message. 464 | // if(verbose) { 465 | // printRubyException(e); 466 | // } 467 | } 468 | flush(); 469 | } 470 | 471 | /* 472 | private void printRubyException(Exception e) { 473 | Throwable t = e; 474 | if(t instanceof RubyException && t.getCause() != null) { 475 | t = t.getCause(); 476 | } 477 | if (t.getCause() instanceof RaiseException) { 478 | t = t.getCause(); 479 | } 480 | String st = Utils.getStackTrace(t); 481 | for (String s : st.split("\n")) { 482 | // strip out some generic jruby error strings that aren't useful: 483 | if (!s.equals(" ...internal jruby stack elided...") && !s.startsWith(" from (unknown).(unknown)(:")) { 484 | error(s); 485 | } 486 | } 487 | } 488 | */ 489 | 490 | private void flush() { 491 | System.out.flush(); 492 | System.err.flush(); 493 | } 494 | 495 | @Override 496 | protected void dblclick() { 497 | try { 498 | if( file != null ) { 499 | String filePath = file.getAbsolutePath(); 500 | info( "Attempting to open file '" + filePath + "'" ); 501 | Process p; 502 | if( MaxSystem.isOsWindows() ) { 503 | // "file editor" is a dummy argument, I guess it controls the window title if a command prompt is opened. 504 | // This dummy argument has to contain spaces or be explicitly wrapped in double quotes. 505 | p = Runtime.getRuntime().exec( new String[]{"cmd", "/c", "start", "file editor", filePath} ); 506 | } 507 | else { 508 | p = Runtime.getRuntime().exec( new String[]{"open", filePath} ); 509 | } 510 | try { 511 | p.waitFor(); 512 | if( p.exitValue() != 0 ) { 513 | err( Utils.getInputStreamAsString( p.getErrorStream() ) ); 514 | } 515 | } catch( InterruptedException e ) { 516 | err( e.getMessage() ); 517 | } 518 | } 519 | } catch( IOException e ) { 520 | throw new RuntimeException( e ); 521 | } 522 | } 523 | 524 | public void createfile() { 525 | MaxPatcher patcher = getParentPatcher(); 526 | if( !Utils.isPatcherSaved( patcher ) ) { 527 | err( "patcher must be saved before creating a file" ); 528 | return; 529 | } 530 | if( filePath == null ) { 531 | err( "@file attribute must be a filename (or relative path)" ); 532 | return; 533 | } 534 | File file = getFileToBeCreated(); 535 | if( file.exists() ) { 536 | err( "File exists \"" + file + "\"" ); 537 | return; 538 | } 539 | info( "creating file: " + file, true ); 540 | try { 541 | if( file.createNewFile() ) { 542 | this.file = file; 543 | } 544 | } catch( IOException e ) { 545 | throw new RuntimeException( e ); 546 | } 547 | } 548 | 549 | private File getFileToBeCreated() { 550 | MaxPatcher patcher = getParentPatcher(); 551 | if( patcher != null ) { 552 | File folder = new File( patcher.getPath() ); 553 | String path = filePath; 554 | if( !path.endsWith( DEFAULT_EXTENSION ) ) { 555 | path += DEFAULT_EXTENSION; 556 | } 557 | return new File( folder, path ); 558 | } 559 | return null; 560 | } 561 | } 562 | -------------------------------------------------------------------------------- /src/jruby4max/maxsupport/Atomizer.java: -------------------------------------------------------------------------------- 1 | package jruby4max.maxsupport; 2 | 3 | import com.cycling74.max.Atom; 4 | 5 | /** 6 | * Interface for objects that can convert themselves to a Max Atom. 7 | * 8 | * @author Adam Murray (adam@compusition.com) 9 | */ 10 | public interface Atomizer { 11 | 12 | Atom toAtom(); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/jruby4max/maxsupport/JRubyMaxObject.java: -------------------------------------------------------------------------------- 1 | package jruby4max.maxsupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import com.cycling74.max.Atom; 31 | import com.cycling74.max.Executable; 32 | import com.cycling74.max.MaxObject; 33 | import com.cycling74.max.MaxQelem; 34 | import jruby4max.rubysupport.IdInUseException; 35 | import jruby4max.rubysupport.MaxRubyAdapter; 36 | import jruby4max.rubysupport.RubyProperties; 37 | import jruby4max.util.Logger; 38 | import jruby4max.util.Utils; 39 | import org.jruby.CompatVersion; 40 | 41 | import java.io.PrintStream; 42 | 43 | /** 44 | * Superclass for objects that support Ruby scripting. 45 | * 46 | * @author Adam Murray (adam@compusition.com) 47 | */ 48 | public abstract class JRubyMaxObject extends MaxObject implements Logger { 49 | 50 | protected String context = null; 51 | protected String id = defaultId(); 52 | private boolean autoinit = false; 53 | 54 | protected Atom[] rubyVersionValue = Atom.newAtom( new String[]{ RubyProperties.DEFAULT_RUBY_VERSION_STRING } ); 55 | protected CompatVersion rubyVersion = RubyProperties.DEFAULT_RUBY_VERSION; 56 | 57 | protected MaxRubyAdapter ruby; 58 | 59 | protected JRubyMaxObject self = this; 60 | 61 | 62 | protected boolean verbose = false; 63 | 64 | protected boolean initialized = false; 65 | protected MaxQelem initializer = getInitializerQelem(); 66 | 67 | 68 | public JRubyMaxObject() { 69 | declareAttribute( "verbose" ); 70 | if( initializer == null ) { 71 | // The initializer should never be null inside Max, but needs to be null in unit tests 72 | initialized = true; 73 | } 74 | else { 75 | initializer.set(); 76 | } 77 | declareAttribute( "context", "getcontext", "context" ); 78 | declareAttribute( "id", "getid", "id" ); 79 | declareAttribute( "autoinit" ); 80 | declareAttribute( "ruby_version", "getruby_version", "ruby_version" ); 81 | } 82 | 83 | private final MaxQelem getInitializerQelem() { 84 | Executable initializer = getInitializer(); 85 | return initializer == null ? null : new MaxQelem( initializer ); 86 | } 87 | 88 | /** 89 | * A subclass can override this method to return a MaxQelem that can do operations not normally available in the 90 | * constructor, such as outputting messages. See discussion at 91 | * http://www.cycling74.com/forums/index.php?t=rview&goto=114649 Note that if this method is overriden, it is the 92 | * responsibility of the initializer code to set initialized = true. 93 | * 94 | * @return an Executable that executes initialization code. Must not be null. 95 | */ 96 | protected Executable getInitializer() { 97 | return new DefaultRubyInitializer(); 98 | } 99 | 100 | protected class DefaultRubyInitializer implements Executable { 101 | public void execute() { 102 | initialized = true; 103 | try { 104 | ruby = new MaxRubyAdapter( self, context, id, rubyVersion ); 105 | } catch(IdInUseException e) { 106 | String availableId = e.getMessage(); 107 | error( "id " + id + " not available. Using: " + availableId ); 108 | id = availableId; 109 | ruby = new MaxRubyAdapter( self, context, id, rubyVersion ); 110 | } 111 | if( autoinit ) { 112 | ruby.init(); 113 | /* Doing this at construction time causes Max to hang for a while if there are many instances of this object. 114 | Thus autoinit is false by default. 115 | The downside to not init'ing here is there will be a slight delay the first time a script tries to evaluate 116 | The hang delay got much shorter with JRuby 1.1. */ 117 | } 118 | } 119 | } 120 | 121 | @Override 122 | protected void notifyDeleted() { 123 | if( ruby != null ) { 124 | ruby.notifyDeleted(); 125 | } 126 | if( initializer != null ) { 127 | initializer.release(); 128 | } 129 | } 130 | 131 | // for use with debugging unit tests, must be set from the test after instantiation 132 | private PrintStream debugOut; 133 | 134 | /** 135 | * Unit tests can set this output stream back to system.out (or a log file) for testing purposes outside of the Max 136 | * environment. Normally any MaxObject will "steal" System.out and set it to the Max output stream (which is not 137 | * actually available outside of Max). 138 | * 139 | * @param debugOut 140 | */ 141 | protected void setDebugOut( PrintStream debugOut ) { 142 | this.debugOut = debugOut; 143 | } 144 | 145 | public void debug( String message ) { 146 | if( debugOut != null ) { 147 | debugOut.println( message ); 148 | } 149 | } 150 | 151 | /** 152 | * Automatically prepends the object name to the beginning of error messages. 153 | * 154 | * @param message the error message 155 | */ 156 | public void info( String message ) { 157 | info( message, false ); 158 | } 159 | 160 | public void info( String message, boolean force ) { 161 | if( verbose || force ) { 162 | post( this.getClass().getName() + ": " + message ); 163 | } 164 | } 165 | 166 | /** 167 | * Automatically prepends the object name to the beginning of error messages. 168 | * 169 | * @param message the error message 170 | */ 171 | public void err( String message ) { 172 | error( this.getClass().getName() + ": " + message ); 173 | } 174 | 175 | public String toString() { 176 | return getClass().getName() + "#<" + Integer.toHexString( hashCode() ) + ">"; 177 | } 178 | 179 | public Atom[] getcontext() { 180 | return Atom.newAtom( new String[]{ context } ); 181 | } 182 | 183 | public String context() { 184 | return context; 185 | } 186 | 187 | public void context( Atom[] params ) { 188 | String context = Utils.toString( params ); 189 | this.context = context; 190 | if( ruby != null ) { 191 | ruby.setContext( context ); 192 | } // else we're still initializing and the initalizer should handle this 193 | } 194 | 195 | public Atom[] getid() { 196 | return Atom.newAtom( new String[]{ id } ); 197 | } 198 | 199 | public String id() { 200 | return id; 201 | } 202 | 203 | public void id( Atom[] params ) { 204 | String id = Utils.toString( params ); 205 | if( id == null || "".equals( id ) ) { 206 | id = defaultId(); 207 | } 208 | this.id = id; 209 | if( ruby != null ) { 210 | try { 211 | ruby.setId( id ); 212 | } catch(IdInUseException e) { 213 | String availableId = e.getMessage(); 214 | error( "id " + id + " not available. Using: " + availableId ); 215 | id = availableId; 216 | ruby.setId( id ); 217 | } 218 | } // else we're still initializing and the initalizer should handle this 219 | } 220 | 221 | private String defaultId() { 222 | return Integer.toHexString( hashCode() ); 223 | } 224 | 225 | public Atom[] getruby_version() { 226 | return rubyVersionValue; 227 | } 228 | 229 | // Using Atom[] is annoying but it avoids an annoying warning in the max menu "coerced float to String" when doing @ruby_version 1.9 230 | public void ruby_version(Atom[] rubyVersionValue) { 231 | if (initialized) { 232 | err("ruby_version cannot be changed. Use @ruby_version when creating the object."); 233 | return; 234 | } 235 | if (rubyVersionValue == null || rubyVersionValue.length < 1) return; 236 | 237 | String rubyVersionString = rubyVersionValue[0].toString(); 238 | // @ruby_version 1.9 comes through as "1.900000" so we have to chop off the trailing zeros 239 | rubyVersionString = rubyVersionString.replaceFirst("(.*?)0+", "$1"); 240 | 241 | CompatVersion rubyVersion = RubyProperties.getRubyVersion(rubyVersionString); 242 | if (rubyVersion != null) { 243 | this.rubyVersion = rubyVersion; 244 | this.rubyVersionValue = Atom.newAtom(new String[]{rubyVersionString}); 245 | } else err("Invalid ruby_version '" + rubyVersionString + "'. Only 1_8 and 1_9 supported."); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/jruby4max/rubysupport/IScriptEvaluator.java: -------------------------------------------------------------------------------- 1 | package jruby4max.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.io.File; 31 | 32 | /** 33 | * Interface for all script evaluator engines. 34 | * 35 | * @author Adam Murray (adam@compusition.com) 36 | */ 37 | public interface IScriptEvaluator { 38 | 39 | void resetContext(); 40 | 41 | boolean isInitialized(); 42 | 43 | void setInitialized( boolean initialized ); 44 | 45 | void declareGlobal( String variableName, Object obj ); 46 | 47 | void undeclareGlobal( String variableName ); 48 | 49 | void setScriptFile( File scriptFile ); 50 | 51 | Object eval( CharSequence rubyCode ); 52 | 53 | void exit(); 54 | } 55 | -------------------------------------------------------------------------------- /src/jruby4max/rubysupport/IdInUseException.java: -------------------------------------------------------------------------------- 1 | package jruby4max.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | /** 31 | * A failure to assign an ID because it was already in use. 32 | *

33 | * The exception message will suggest an available id. 34 | * 35 | * @author Adam Murray (adam@compusition.com) 36 | */ 37 | @SuppressWarnings("serial") 38 | public class IdInUseException extends RuntimeException { 39 | 40 | public IdInUseException( String availableId ) { 41 | super( availableId ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/jruby4max/rubysupport/InputConversionOption.java: -------------------------------------------------------------------------------- 1 | package jruby4max.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2010, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | public enum InputConversionOption { 31 | 32 | /** 33 | * Convert incoming Max symbols to Ruby single-quoted strings 34 | */ 35 | STRING( "string" ), 36 | 37 | /** 38 | * Convert to Ruby double-quoted strings 39 | */ 40 | INTERPOLATED( "interpolated" ), 41 | 42 | /** 43 | * Convert incoming Max symbols to Ruby symbols 44 | */ 45 | SYMBOL( "symbol" ), 46 | 47 | /** 48 | * Don't modify incoming Max symbols (so they can be interpreted as literal numbers, reference method names, etc) 49 | */ 50 | LITERAL( "literal" ), 51 | 52 | /** 53 | * Apply the previous setting to the remaining inlets 54 | */ 55 | REMAINING_INLETS( "*" ); 56 | 57 | public static InputConversionOption DEFAULT = LITERAL; 58 | 59 | InputConversionOption( String stringValue ) { 60 | this.stringValue = stringValue; 61 | } 62 | 63 | /** 64 | * The string value of this enumerated value. 65 | */ 66 | public String toString() { 67 | return stringValue; 68 | } 69 | 70 | private final String stringValue; 71 | 72 | public static InputConversionOption fromString( String stringValue ) { 73 | if( STRING.stringValue.equals( stringValue ) ) { 74 | return STRING; 75 | } 76 | else if ( INTERPOLATED.stringValue.equals( stringValue ) ) { 77 | return INTERPOLATED; 78 | } 79 | else if( SYMBOL.stringValue.equals( stringValue ) ) { 80 | return SYMBOL; 81 | } 82 | else if( LITERAL.stringValue.equals( stringValue ) ) { 83 | return LITERAL; 84 | } 85 | else if( REMAINING_INLETS.stringValue.equals( stringValue ) ) { 86 | return REMAINING_INLETS; 87 | } 88 | else { 89 | return null; 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/jruby4max/rubysupport/MaxRubyAdapter.java: -------------------------------------------------------------------------------- 1 | package jruby4max.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import com.cycling74.max.Atom; 31 | import com.cycling74.max.MaxObject; 32 | import jruby4max.maxsupport.Atomizer; 33 | import jruby4max.util.GlobalVariableStore; 34 | import jruby4max.util.LineBuilder; 35 | import jruby4max.util.Logger; 36 | import jruby4max.util.Utils; 37 | import org.jruby.CompatVersion; 38 | import org.jruby.RubyArray; 39 | import org.jruby.RubyHash; 40 | import org.jruby.RubySymbol; 41 | 42 | import java.io.File; 43 | import java.io.IOException; 44 | import java.math.BigInteger; 45 | 46 | /** 47 | * The bridge between Max and Ruby. 48 | * 49 | * @author Adam Murray (adam@compusition.com) 50 | */ 51 | public class MaxRubyAdapter { 52 | 53 | public static final String NIL = "nil"; 54 | 55 | private IScriptEvaluator ruby; 56 | 57 | private LineBuilder code = new LineBuilder(); 58 | 59 | private LineBuilder fileInit = new LineBuilder(); 60 | 61 | private final MaxObject maxObject; 62 | 63 | private Logger logger; 64 | 65 | private String maxContext; 66 | 67 | private String id; 68 | 69 | private CompatVersion rubyVersion; 70 | 71 | public MaxRubyAdapter( MaxObject maxObject, String context, String id, CompatVersion rubyVersion ) { 72 | this.maxObject = maxObject; 73 | if( maxObject instanceof Logger ) { 74 | this.logger = (Logger)maxObject; 75 | } 76 | this.maxContext = context; 77 | this.id = id; 78 | this.rubyVersion = rubyVersion; 79 | } 80 | 81 | private void getEvaluator() { 82 | ruby = ScriptEvaluatorManager.getRubyEvaluator( maxContext, id, maxObject, rubyVersion ); 83 | ruby.declareGlobal( "max_ruby_adapter", this ); 84 | ruby.declareGlobal( "global_variable_store", GlobalVariableStore.getInstance() ); 85 | } 86 | 87 | public Logger getLogger() { 88 | return logger; 89 | } 90 | 91 | public void setLogger( Logger logger ) { 92 | this.logger = logger; 93 | } 94 | 95 | public String getContext() { 96 | return maxContext; 97 | } 98 | 99 | public void setContext( String context ) { 100 | if( !Utils.equals( this.maxContext, context ) ) { 101 | // cleanup old context 102 | ScriptEvaluatorManager.removeRubyEvaluator( maxObject ); 103 | 104 | // init new context 105 | this.maxContext = context; 106 | getEvaluator(); 107 | } 108 | } 109 | 110 | public String getId() { 111 | return id; 112 | } 113 | 114 | public void setId( String id ) { 115 | if( !Utils.equals( this.id, id ) ) { 116 | ScriptEvaluatorManager.updateId( maxObject, id ); 117 | this.id = id; 118 | } 119 | } 120 | 121 | public void notifyDeleted() { 122 | ScriptEvaluatorManager.removeRubyEvaluator( maxObject ); 123 | } 124 | 125 | public void exec( CharSequence rubyCode ) { 126 | eval( rubyCode, false ); 127 | } 128 | 129 | public Object eval( CharSequence rubyCode ) { 130 | return eval( rubyCode, true ); 131 | } 132 | 133 | /** 134 | * @return an Atom or Atom[], it's up to the calling code to check the type 135 | */ 136 | public Object eval( CharSequence rubyCode, boolean returnResult ) { 137 | if( ruby == null || !ruby.isInitialized() ) { 138 | init(); 139 | } 140 | Object result; 141 | synchronized(ruby) { 142 | // Set the $MaxObject/ID globals correctly in shared contexts: 143 | ruby.undeclareGlobal( "max_object" ); 144 | ruby.declareGlobal( "max_object", maxObject ); 145 | result = ruby.eval( rubyCode ); 146 | } 147 | if( returnResult ) { 148 | return toAtoms( result, true ); 149 | } 150 | else { 151 | return null; 152 | } 153 | } 154 | 155 | public void init() { 156 | init( null, Atom.emptyArray ); 157 | } 158 | 159 | public void init( File file, Atom[] args ) { 160 | if (ruby == null) { 161 | getEvaluator(); 162 | } 163 | if( code.isEmpty() ) { 164 | // setup the $LOAD_PATH 165 | for( String loadPath : RubyProperties.getLoadPaths() ) { 166 | code.line( "$: << " + quote( loadPath ) ); 167 | } 168 | // and include any initializer files: 169 | for( String initializer : RubyProperties.getInitializerFiles() ) { 170 | File initFile = Utils.getFile( initializer ); 171 | String initFileFolderPath = pathToContainerFolder( initFile ); 172 | code.line( "$: << " + quote( initFileFolderPath ) ); 173 | String initializationCode = Utils.getFileAsString( initFile ); 174 | code.append( initializationCode ); 175 | } 176 | } 177 | 178 | if( ruby.isInitialized() ) { 179 | ScriptEvaluatorManager.notifyContextDestroyedListener( maxContext, maxObject ); 180 | ruby.resetContext(); 181 | // TODO: this prevents using multiple objects with @file from sharing the same @context, which is not really desirable. 182 | // But we need to do this for @autowatch. 183 | } 184 | ruby.setInitialized( true ); 185 | 186 | ruby.setScriptFile( file ); 187 | exec( code ); 188 | 189 | if( file != null ) { 190 | String script = Utils.getFileAsString( file ); 191 | fileInit.clear(); 192 | 193 | // include the folder containing the @file, so scripts can require files relative to themselves 194 | String fileFolderPath = pathToContainerFolder( file ); 195 | fileInit.line( "$: << " + quote( fileFolderPath ) ); 196 | 197 | // set $0. I'd like to set __FILE__ here too, but that variable cannot be assigned 198 | //fileInit.line( "$0 = " + quote( file.getAbsolutePath() ) ); 199 | 200 | // any set the arguments 201 | for( Atom arg : args ) { 202 | fileInit.line( "$* << " + Utils.detokenize( arg ) ); 203 | } 204 | 205 | exec( fileInit ); 206 | exec( script ); 207 | } 208 | } 209 | 210 | private String pathToContainerFolder( File file ) { 211 | File folder = file.getParentFile(); 212 | if( folder != null ) { 213 | String folderPath; 214 | try { 215 | folderPath = folder.getCanonicalPath(); 216 | } catch(IOException e) { 217 | System.err.println( e ); 218 | folderPath = folder.getAbsolutePath(); 219 | } 220 | return folderPath; 221 | } 222 | return null; 223 | } 224 | 225 | private String quote( Object o ) { 226 | if( o == null ) return NIL; 227 | String s = o.toString(); 228 | if( s == null ) return NIL; 229 | return "'" + s.replace( "\\", "\\\\" ).replace( "'", "\\'" ) + "'"; 230 | } 231 | 232 | /** 233 | * Converts the result of a Ruby evaluation into Max data types (Atoms) 234 | * 235 | * @param obj - 236 | * A Ruby value 237 | * @return an Atom or an Atom[]. The calling code needs to figure out what type this is and handle it appropriately 238 | */ 239 | public Object toAtoms( Object obj ) { 240 | return toAtoms( obj, logger ); 241 | } 242 | 243 | public Object toAtoms( Object obj, boolean logCoercions ) { 244 | return toAtoms( obj, (logCoercions ? logger : null) ); 245 | } 246 | 247 | private static BigInteger MAX_BINT = new BigInteger( Integer.MAX_VALUE + "" ); 248 | private static BigInteger MIN_BINT = new BigInteger( Integer.MIN_VALUE + "" ); 249 | 250 | private Object toAtoms( Object obj, Logger logger ) { 251 | if( obj == null ) { 252 | return Atom.newAtom( "nil" ); 253 | } 254 | else if( obj instanceof Atom || obj instanceof Atom[] ) { 255 | return obj; 256 | } 257 | else if( obj instanceof Atomizer ) { 258 | return ((Atomizer)obj).toAtom(); 259 | } 260 | else if( obj instanceof Double || obj instanceof Float ) { 261 | // Not sure if there's a situation where we should coerce to a String, 262 | // because Max can only handle floats and JRuby always outputs Doubles. 263 | // Floating point accuracy is very different from the Long wrap-around problem, 264 | // so letting it downcast seems ok: 265 | return Atom.newAtom( ((Number)obj).doubleValue() ); 266 | } 267 | else if( obj instanceof Long || obj instanceof Integer || obj instanceof Short || obj instanceof Byte ) { 268 | long val = ((Number)obj).longValue(); 269 | if( val > Integer.MAX_VALUE || val < Integer.MIN_VALUE ) { 270 | if( logger != null ) { 271 | logger.info( "coerced type " + obj.getClass().getName() + " to String" ); 272 | } 273 | return Atom.newAtom( obj.toString() ); 274 | } 275 | else return Atom.newAtom( val ); 276 | } 277 | else if( obj instanceof BigInteger ) { 278 | BigInteger bigInt = (BigInteger)obj; 279 | if( bigInt.compareTo( MAX_BINT ) > 0 || bigInt.compareTo( MIN_BINT ) < 0 ) { 280 | if( logger != null ) { 281 | logger.info( "coerced type " + obj.getClass().getName() + " to String" ); 282 | } 283 | return Atom.newAtom( obj.toString() ); 284 | } 285 | else return Atom.newAtom( bigInt.intValue() ); 286 | } 287 | else if( obj instanceof CharSequence ) { 288 | return Atom.newAtom( obj.toString() ); 289 | } 290 | else if( obj instanceof RubySymbol ) { 291 | return Atom.newAtom( ":" + obj ); 292 | } 293 | else if( obj instanceof Boolean ) { 294 | return Atom.newAtom( ((Boolean)obj).booleanValue() ); 295 | } 296 | else if( obj instanceof RubyArray ) { 297 | RubyArray array = (RubyArray)obj; 298 | 299 | Object[] atomsArray = new Object[array.size()]; 300 | boolean isFlatArray = true; 301 | for( int i = 0; i < array.size(); i++ ) { 302 | Object val = toAtoms( array.get( i ), logger ); 303 | if( !(val instanceof Atom) ) { 304 | isFlatArray = false; 305 | } 306 | atomsArray[i] = val; 307 | } 308 | 309 | if( isFlatArray ) { 310 | Atom[] atoms = new Atom[array.size()]; 311 | for( int i = 0; i < atomsArray.length; i++ ) { 312 | atoms[i] = (Atom)atomsArray[i]; 313 | } 314 | return atoms; 315 | } 316 | else { 317 | if( logger != null ) { 318 | logger.info( "coerced a nested Array to String" ); 319 | } 320 | return Atom.newAtom( toArrayString( atomsArray ) ); 321 | } 322 | } 323 | else if( obj instanceof RubyHash ) { 324 | if( logger != null ) { 325 | logger.info( "coerced a Hash to String" ); 326 | } 327 | RubyHash hash = (RubyHash)obj; 328 | StringBuilder s = new StringBuilder(); 329 | for( Object key : hash.keySet() ) { 330 | if( s.length() > 0 ) { 331 | s.append( ", " ); 332 | } 333 | s.append( toArrayString( toAtoms( key ) ) ); 334 | s.append( "=>" ); 335 | s.append( toArrayString( toAtoms( hash.get( key ) ) ) ); 336 | } 337 | s.insert( 0, "{" ); 338 | s.append( "}" ); 339 | return new Atom[]{ Atom.newAtom( s.toString() ) }; 340 | } 341 | else { 342 | if( logger != null ) { 343 | logger.info( "coerced type " + obj.getClass().getName() + " to String" ); 344 | } 345 | return Atom.newAtom( obj.toString() ); 346 | } 347 | 348 | } 349 | 350 | private String toArrayString( Object o ) { 351 | StringBuilder s = new StringBuilder(); 352 | buildArrayString( o, s ); 353 | return s.toString(); 354 | } 355 | 356 | private void buildArrayString( Object o, StringBuilder s ) { 357 | if( o instanceof Object[] ) { 358 | s.append( "[" ); 359 | Object[] objs = (Object[])o; 360 | for( int i = 0; i < objs.length; i++ ) { 361 | if( i > 0 ) { 362 | s.append( "," ); 363 | } 364 | buildArrayString( objs[i], s ); 365 | } 366 | s.append( "]" ); 367 | } 368 | else { 369 | s.append( o.toString() ); 370 | } 371 | } 372 | 373 | /* 374 | public void on_context_destroyed(Object callback) { 375 | ScriptEvaluatorManager.registerContextDestroyedListener(maxObject, callback.toString()); 376 | } 377 | */ 378 | } 379 | -------------------------------------------------------------------------------- /src/jruby4max/rubysupport/RubyException.java: -------------------------------------------------------------------------------- 1 | package jruby4max.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | /** 31 | * Wrapper for any Ruby exceptions. Helps encapsulate the underlying Ruby implementation. 32 | * 33 | * @author Adam Murray (adam@compusition.com) 34 | */ 35 | @SuppressWarnings("serial") 36 | public class RubyException extends RuntimeException { 37 | 38 | public RubyException() { 39 | super(); 40 | } 41 | 42 | public RubyException( String message, Throwable cause ) { 43 | super( message, cause ); 44 | } 45 | 46 | public RubyException( String message ) { 47 | super( message ); 48 | } 49 | 50 | public RubyException( Throwable cause ) { 51 | super( cause ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/jruby4max/rubysupport/RubyProperties.java: -------------------------------------------------------------------------------- 1 | package jruby4max.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import com.cycling74.max.MaxSystem; 31 | import jruby4max.util.Utils; 32 | import org.jruby.CompatVersion; 33 | 34 | import java.io.File; 35 | import java.io.FileInputStream; 36 | import java.io.IOException; 37 | import java.util.Properties; 38 | 39 | /** 40 | * Manages all global settings for Ruby evaluation. 41 | * 42 | * @author Adam Murray (adam@compusition.com) 43 | */ 44 | public class RubyProperties { 45 | 46 | public static Properties properties; 47 | 48 | public static final String DEFAULT_INITIALIZER_FILES = "jruby_initialize.rb"; 49 | 50 | private static String[] initializers; 51 | 52 | private static String[] loadpaths; 53 | 54 | static { 55 | // Initialize JRuby system properties and load the jruby_for_max.properties file 56 | try { 57 | String propsPath = MaxSystem.locateFile( "jruby_for_max.properties" ); 58 | if( propsPath == null ) { 59 | //MaxSystem.error("jruby4max.ruby.properties not found! Maybe jruby4max objects was not installed correctly?"); 60 | properties = new Properties(); 61 | } 62 | else { 63 | File propFile = new File( propsPath ); 64 | properties = new Properties(); 65 | try { 66 | properties.load( new FileInputStream( propFile ) ); 67 | } catch(IOException e) { 68 | throw new RuntimeException( e ); 69 | } 70 | 71 | String jrubyHome = properties.getProperty( "jruby.home" ); 72 | if( jrubyHome != null ) { 73 | System.setProperty( "jruby.home", jrubyHome ); 74 | } 75 | } 76 | } catch(UnsatisfiedLinkError e) { 77 | // we're running outside of Max, probably for unit testing 78 | // can't call System.out here, Max stole it and it will just cause another UnsatisfiedLinkError 79 | // System.out.println("Using hard-coded defaults for RubyProperties."); 80 | properties = new Properties(); 81 | } 82 | 83 | String gemHome = properties.getProperty("gem.home"); 84 | if (gemHome == null) { 85 | properties.setProperty("gem.home", defaultGemHome()); 86 | } 87 | String gemPath = properties.getProperty("gem.path"); 88 | if (gemPath == null) { 89 | properties.setProperty("gem.path", defaultGemHome()); 90 | } 91 | } 92 | 93 | public static String[] getInitializerFiles() { 94 | if( initializers == null ) { 95 | initializers = properties.getProperty( "ruby.initializers", DEFAULT_INITIALIZER_FILES ).split( ";" ); 96 | for( int i = 0; i < initializers.length; i++ ) { 97 | initializers[i] = initializers[i].trim(); 98 | } 99 | } 100 | return initializers; 101 | } 102 | 103 | public static String[] getLoadPaths() { 104 | if( loadpaths == null ) { 105 | String loadpathsProp = properties.getProperty( "ruby.loadpaths" ); 106 | if( loadpathsProp == null || loadpathsProp.trim().equals( "" ) ) { 107 | loadpaths = new String[]{ }; 108 | } 109 | else { 110 | loadpaths = loadpathsProp.split( ";" ); 111 | for( int i = 0; i < loadpaths.length; i++ ) { 112 | loadpaths[i] = loadpaths[i].trim(); 113 | } 114 | } 115 | } 116 | return loadpaths; 117 | } 118 | 119 | public static CompatVersion getRubyVersion( String version ) { 120 | if( "1.9".equals( version ) ) { 121 | return CompatVersion.RUBY1_9; 122 | } 123 | else if( "1.8".equals( version ) ) { 124 | return CompatVersion.RUBY1_8; 125 | } 126 | else return null; 127 | } 128 | 129 | public static String DEFAULT_RUBY_VERSION_STRING = "1.9"; 130 | public static CompatVersion DEFAULT_RUBY_VERSION = CompatVersion.RUBY1_9; 131 | 132 | public static String defaultGemHome() { 133 | return Utils.pathToContainingFolder(MaxSystem.locateFile("jruby.jar")) + File.separatorChar + "jruby_gems"; 134 | } 135 | 136 | public static String getGemHome() { 137 | return properties.getProperty("gem.home"); 138 | } 139 | 140 | public static String getGemPath() { 141 | return properties.getProperty("gem.path"); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/jruby4max/rubysupport/ScriptEvaluator.java: -------------------------------------------------------------------------------- 1 | package jruby4max.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008-2011, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import org.jruby.CompatVersion; 31 | import org.jruby.embed.LocalContextScope; 32 | import org.jruby.embed.LocalVariableBehavior; 33 | import org.jruby.embed.ScriptingContainer; 34 | 35 | import java.io.File; 36 | import java.util.HashMap; 37 | import java.util.Map; 38 | 39 | public class ScriptEvaluator implements IScriptEvaluator { 40 | 41 | private ScriptingContainer container; 42 | 43 | private CompatVersion compatVersion; 44 | 45 | private boolean initialized = false; 46 | private Map persitentGlobals = new HashMap(); 47 | 48 | public ScriptEvaluator( CompatVersion rubyVersion ) { 49 | // if compatVersion isn't set right away, it won't work (maybe because of the global var setup?) 50 | compatVersion = rubyVersion; 51 | resetEngineContext(); 52 | } 53 | 54 | public void resetContext() { 55 | resetEngineContext(); 56 | try { 57 | for( Map.Entry global : persitentGlobals.entrySet() ) { 58 | String name = global.getKey(); 59 | undeclareGlobalInternal( name ); 60 | declareGlobalInternal( name, global.getValue() ); 61 | } 62 | } catch(Exception e) { 63 | throw new RubyException( e ); 64 | } 65 | } 66 | 67 | public boolean isInitialized() { 68 | return initialized; 69 | } 70 | 71 | public void setInitialized( boolean initialized ) { 72 | this.initialized = initialized; 73 | } 74 | 75 | public void declareGlobal( String variableName, Object obj ) { 76 | try { 77 | declareGlobalInternal( variableName, obj ); 78 | } catch(Exception e) { 79 | throw new RubyException( e ); 80 | } 81 | persitentGlobals.put( variableName, obj ); 82 | } 83 | 84 | public void undeclareGlobal( String variableName ) { 85 | try { 86 | undeclareGlobalInternal( variableName ); 87 | } catch(Exception e) { 88 | throw new RubyException( e ); 89 | } 90 | persitentGlobals.remove( variableName ); 91 | } 92 | 93 | protected void resetEngineContext() { 94 | container = new ScriptingContainer( LocalContextScope.SINGLETHREAD, LocalVariableBehavior.TRANSIENT ); 95 | container.setCompatVersion( compatVersion ); 96 | container.setCurrentDirectory( System.getProperty( "user.home" ) ); 97 | 98 | // It seems we can't directly modify the current environment, so we need to make a copy first. 99 | @SuppressWarnings("unchecked") 100 | Map env = new HashMap(container.getEnvironment()); 101 | env.put("GEM_HOME", RubyProperties.getGemHome()); 102 | env.put("GEM_PATH", RubyProperties.getGemPath()); 103 | container.setEnvironment(env); 104 | } 105 | 106 | protected void declareGlobalInternal( String variableName, Object obj ) { 107 | container.put( "$" + variableName, obj ); 108 | } 109 | 110 | protected void undeclareGlobalInternal( String variableName ) { 111 | container.removeAttribute( "$" + variableName ); 112 | } 113 | 114 | public void setScriptFile( File file ) { 115 | if( file == null ) { 116 | container.setScriptFilename( "" ); 117 | } 118 | else { 119 | container.setScriptFilename( file.getAbsolutePath() ); 120 | } 121 | } 122 | 123 | public Object eval( CharSequence rubyCode ) { 124 | return container.runScriptlet( rubyCode.toString() ); 125 | } 126 | 127 | public void exit() { 128 | container.terminate(); 129 | container = null; 130 | 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/jruby4max/rubysupport/ScriptEvaluatorManager.java: -------------------------------------------------------------------------------- 1 | package jruby4max.rubysupport; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import jruby4max.util.MappedSet; 31 | import org.jruby.CompatVersion; 32 | 33 | import java.util.HashMap; 34 | import java.util.Map; 35 | import java.util.Set; 36 | 37 | /** 38 | * Factory for Ruby evaluators that manages shared contexts. 39 | * 40 | * Every Ruby evaluator maintains its own set of class/method definitions, and global/constant/attribute variables. 41 | * Each evaluator is uniquely identified by a context String. This context is either the @context attribute of 42 | * the Max object, or is generated if no @context is provided. Additionally, every MaxObject has a unique @id, which 43 | * is stored in the data structures here. 44 | * 45 | * This class provides evaluator instances and keeps track of which evaluator goes with which MaxObject. 46 | * 47 | * The maxObjectMap in this class is expose to all Ruby scripts via the $max_object_map global variable. 48 | * 49 | * @author Adam Murray (adam@compusition.com) 50 | */ 51 | public class ScriptEvaluatorManager { 52 | 53 | private static Map evaluatorContexts = new HashMap(); 54 | 55 | /** 56 | * Map @context => number of evaluators currently using 57 | */ 58 | private static Map evaluatorContextCounter = new HashMap(); 59 | 60 | /** 61 | * Map @context => a Set of MaxObjects 62 | */ 63 | private static MappedSet objectsUsingEvaluator = new MappedSet(); 64 | 65 | /** 66 | * Map @context => a Map of ( @id => MaxObject ) 67 | */ 68 | private static Map> maxObjectMap = new HashMap>(); 69 | 70 | /** 71 | * Stores a mapping from max objects to their @context and @id 72 | */ 73 | private static Map objectMetadata = new HashMap(); 74 | 75 | // This is a singleton (or more accurately: a static utility class), so no instances allowed to be constructed. 76 | private ScriptEvaluatorManager() { 77 | } 78 | 79 | /** 80 | * Get a Ruby evaluator for the specified context. 81 | * 82 | * @return an implementation of IScriptEvaluator 83 | */ 84 | public static IScriptEvaluator getRubyEvaluator( String maxContext, String id, Object maxObject, CompatVersion rubyVersion ) { 85 | 86 | Map idMap = maxObjectMap.get( maxContext ); 87 | if( idMap == null ) { 88 | idMap = new HashMap(); 89 | maxObjectMap.put( maxContext, idMap ); 90 | } 91 | else { 92 | ensureIdAvailable( idMap, maxObject, id ); 93 | } 94 | idMap.put( id, maxObject ); 95 | objectMetadata.put( maxObject, new String[]{ maxContext, id } ); 96 | 97 | // if maxContext is null, the evaluatorContext will be some semi-random unique context 98 | String evaluatorContext = getEvaluatorContext( maxContext, maxObject ); 99 | 100 | IScriptEvaluator evaluator = evaluatorContexts.get( evaluatorContext ); 101 | if( evaluator == null ) { 102 | evaluator = new ScriptEvaluator( rubyVersion ); 103 | evaluatorContexts.put( evaluatorContext, evaluator ); 104 | evaluatorContextCounter.put( evaluatorContext, 1 ); 105 | Set maxObjects = objectsUsingEvaluator.addValue( evaluatorContext, maxObject ); 106 | evaluator.declareGlobal( "max_objects", maxObjects ); 107 | evaluator.declareGlobal( "max_object_map", maxObjectMap ); 108 | } 109 | else { 110 | int count = evaluatorContextCounter.get( evaluatorContext ); 111 | count++; 112 | evaluatorContextCounter.put( evaluatorContext, count ); 113 | objectsUsingEvaluator.addValue( evaluatorContext, maxObject ); 114 | } 115 | return evaluator; 116 | } 117 | 118 | /** 119 | * Signal that a MaxObject was destroyed, and terminate the evaluator for this MaxObject if no other objects are 120 | * referencing it. 121 | */ 122 | public static void removeRubyEvaluator( Object maxObject ) { 123 | String[] contextAndId = objectMetadata.remove( maxObject ); 124 | if( contextAndId != null ) { 125 | String maxContext = contextAndId[0]; 126 | String evaluatorContext = getEvaluatorContext( maxContext, maxObject ); 127 | String id = contextAndId[1]; 128 | int count = evaluatorContextCounter.get( evaluatorContext ); 129 | count--; 130 | if( count > 0 ) { 131 | evaluatorContextCounter.put( evaluatorContext, count ); 132 | objectsUsingEvaluator.get( evaluatorContext ).remove( maxObject ); 133 | } 134 | else { 135 | notifyContextDestroyedListener( maxContext, maxObject ); 136 | objectsUsingEvaluator.remove( evaluatorContext ); 137 | evaluatorContextCounter.remove( evaluatorContext ); 138 | evaluatorContexts.remove( evaluatorContext ); 139 | } 140 | 141 | // We have to manage the map this way instead of relying on the count, because 142 | // the null maxContext means a different evaluatorContext for each object, 143 | // so the counts don't accurately reflect what's in this map 144 | Map idMap = maxObjectMap.get( maxContext ); 145 | idMap.remove( id ); 146 | if( idMap.isEmpty() ) { 147 | maxObjectMap.remove( maxContext ); 148 | } 149 | } 150 | } 151 | 152 | /** 153 | * Update the id for an existing MaxObject 154 | */ 155 | public static void updateId( Object maxObject, String id ) { 156 | String[] contextAndId = objectMetadata.get( maxObject ); 157 | if( contextAndId != null ) { 158 | String maxContext = contextAndId[0]; 159 | String oldId = contextAndId[1]; 160 | 161 | Map idMap = maxObjectMap.get( maxContext ); 162 | ensureIdAvailable( idMap, maxObject, id ); 163 | 164 | idMap.remove( oldId ); 165 | idMap.put( id, maxObject ); 166 | contextAndId[1] = id; 167 | } 168 | } 169 | 170 | /** 171 | * Validate that an id is unique within a context. 172 | * @param idMap - a map from @id to MaxObject within a specific context 173 | * @param maxObject - the MaxObject requesting this id (it's ok for a MaxObject to set its id to its current id) 174 | * @param id - the requested id for this MaxObject 175 | * @throws IdInUseException if the @id is not available. The message of the Exception provides an available id. 176 | */ 177 | protected static void ensureIdAvailable( Map idMap, Object maxObject, String id ) { 178 | Object existingObject = idMap.get( id ); 179 | if( existingObject != null && !existingObject.equals( maxObject ) ) { 180 | String base = id; 181 | long index = 0; 182 | if( id.matches( ".*\\[\\d*\\]$" ) ) { 183 | int split = id.lastIndexOf( '[' ); 184 | base = id.substring( 0, split ); 185 | String indexStr = id.substring( split + 1 ); 186 | indexStr = indexStr.substring( 0, indexStr.length() - 1 ); 187 | index = Long.parseLong( indexStr ); 188 | } 189 | String suggest; 190 | do { 191 | index++; 192 | suggest = base + "[" + index + "]"; 193 | } while( idMap.containsKey( suggest ) ); 194 | throw new IdInUseException( suggest ); 195 | } 196 | } 197 | 198 | /** 199 | * Either return the requested context, or generate one. 200 | * @param maxContext - the requested context. May be null in which case a context is generated. 201 | * @param maxObject - the MaxObject which will be using thie context. 202 | * @return the context String 203 | */ 204 | protected static String getEvaluatorContext( String maxContext, Object maxObject ) { 205 | if( maxContext == null ) { 206 | return "__" + Integer.toHexString( maxObject.hashCode() ); 207 | } 208 | else { 209 | return maxContext; 210 | } 211 | } 212 | 213 | /** 214 | * Notify a ruby evaluator that a MaxObject has been destroyed. 215 | * 216 | * NOTE: typically you should use removeRubyEvaluator() instead, but this method is exposed publicly for 217 | * the scenario where a MaxObject is being reset when reloading a @file. 218 | * 219 | * @param maxContext - the context String that identifies a specific evaluator 220 | * @param maxObject - the MaxObject being destroyed 221 | */ 222 | public static void notifyContextDestroyedListener( String maxContext, Object maxObject ) { 223 | String evaluatorContext = getEvaluatorContext( maxContext, maxObject ); 224 | IScriptEvaluator ruby = evaluatorContexts.get( evaluatorContext ); 225 | if( ruby != null ) { 226 | ruby.exit(); 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/jruby4max/util/DummyMaxObject.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | import com.cycling74.max.MaxObject; 4 | 5 | /** 6 | * For Unit Testing 7 | */ 8 | public class DummyMaxObject extends MaxObject { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/jruby4max/util/FileWatcher.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | /* 4 | Copyright (c) 2008-2010, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import com.cycling74.max.Executable; 31 | 32 | import java.io.File; 33 | 34 | /** 35 | * File watch mechanism for Max objects. Executes a callback when the file modified date changes. 36 | * 37 | * @author Adam Murray (adam@compusition.com) 38 | */ 39 | 40 | public class FileWatcher extends Thread { 41 | 42 | public final static long DEFAULT_WATCH_PERIOD = 2500; // 2.5 seconds, for no particular reason 43 | 44 | private File file; 45 | 46 | private long prevLastModified; 47 | 48 | private long watchPeriod; 49 | 50 | private Executable callback; 51 | 52 | private volatile boolean threadPaused = false; 53 | 54 | private Thread thisThread; 55 | 56 | public FileWatcher( File file, Executable callback ) { 57 | this( file, callback, DEFAULT_WATCH_PERIOD ); 58 | } 59 | 60 | public FileWatcher( File file, Executable callback, long watchPeriod ) { 61 | setFile( file ); 62 | setCallback( callback ); 63 | setWatchPeriod( watchPeriod ); 64 | } 65 | 66 | public void setFile( File file ) { 67 | this.file = file; 68 | if( file == null ) { 69 | prevLastModified = -1; 70 | } 71 | else { 72 | prevLastModified = file.lastModified(); 73 | } 74 | } 75 | 76 | public File getFile() { 77 | return file; 78 | } 79 | 80 | public long getWatchPeriod() { 81 | return watchPeriod; 82 | } 83 | 84 | public void setWatchPeriod( long watchPeriod ) { 85 | this.watchPeriod = watchPeriod; 86 | } 87 | 88 | public Executable getCallback() { 89 | return callback; 90 | } 91 | 92 | public void setCallback( Executable callback ) { 93 | this.callback = callback; 94 | } 95 | 96 | public void run() { 97 | thisThread = Thread.currentThread(); 98 | while( true ) { 99 | if( file != null ) { 100 | long currLastMod = file.lastModified(); 101 | // System.out.println("Last modified = " + currLastMod); 102 | if( currLastMod > prevLastModified ) { 103 | try { 104 | callback.execute(); 105 | } catch(Exception e) { 106 | if( e.getMessage() != null ) { 107 | // not sure why the message would be null, but I've seen it happen occasionally 108 | System.err.println( e.getMessage() ); 109 | } 110 | } 111 | prevLastModified = currLastMod; 112 | } 113 | try { 114 | Thread.sleep( watchPeriod ); 115 | if( threadPaused ) { 116 | synchronized(this) { 117 | while( threadPaused ) 118 | wait(); // until notify(); 119 | } 120 | } 121 | } catch(InterruptedException e) { 122 | // System.err.println("FileWatcher interrupted (harmless, but probably shouldn't have happened)"); 123 | // e.printStackTrace(); 124 | } 125 | } 126 | } 127 | } 128 | 129 | public synchronized void stopWatching() { 130 | pauseWatching(); 131 | } 132 | 133 | public synchronized void pauseWatching() { 134 | threadPaused = true; 135 | } 136 | 137 | public synchronized void resumeWatching() { 138 | threadPaused = false; 139 | thisThread.notify(); // resume from wait(); 140 | } 141 | 142 | public synchronized boolean isPaused() { 143 | return threadPaused; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/jruby4max/util/GlobalVariableStore.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | 33 | /** 34 | * A singleton map for sharing global variables across any object in the JVM. 35 | * 36 | * @author Adam Murray (adam@compusition.com) 37 | */ 38 | public class GlobalVariableStore { 39 | 40 | private static HashMap instance; 41 | 42 | private GlobalVariableStore() { 43 | } 44 | 45 | public synchronized static Map getInstance() { 46 | if( instance == null ) { 47 | instance = new HashMap(); 48 | } 49 | return instance; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/jruby4max/util/LineBuilder.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | /** 31 | * Builds text one line at a time. 32 | * 33 | * @author Adam Murray (adam@compusition.com) 34 | */ 35 | public class LineBuilder implements CharSequence { 36 | 37 | private StringBuilder lines = new StringBuilder(); 38 | 39 | public void line( String s ) { 40 | lines.append( s ).append( "\n" ); 41 | } 42 | 43 | public void append( String s ) { 44 | lines.append( s ); 45 | } 46 | 47 | public String toString() { 48 | return lines.toString(); 49 | } 50 | 51 | public boolean isEmpty() { 52 | return lines.length() == 0; 53 | } 54 | 55 | public int length() { 56 | return lines.length(); 57 | } 58 | 59 | public void clear() { 60 | lines.setLength( 0 ); 61 | } 62 | 63 | public char charAt( int index ) { 64 | return lines.charAt( index ); 65 | } 66 | 67 | public CharSequence subSequence( int start, int end ) { 68 | return lines.subSequence( start, end ); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/jruby4max/util/Logger.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | /** 31 | * Simple logging interface. 32 | * 33 | * @author Adam Murray (adam@compusition.com) 34 | */ 35 | public interface Logger { 36 | 37 | void debug( String message ); 38 | 39 | void info( String message ); 40 | 41 | void err( String message ); 42 | } 43 | -------------------------------------------------------------------------------- /src/jruby4max/util/MappedSet.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.LinkedHashSet; 31 | import java.util.Set; 32 | import java.util.TreeMap; 33 | 34 | /** 35 | * A Map where all the values are a Set. The Set maintains insertion order of its entires. 36 | * 37 | * @author Adam Murray (adam@compusition.com) 38 | */ 39 | @SuppressWarnings("serial") 40 | public class MappedSet extends TreeMap> { 41 | 42 | public Set addValue( K key, T value ) { 43 | Set values = get( key ); 44 | if( values == null ) { 45 | // LinkedHashSet maintains insertion order 46 | values = new LinkedHashSet(); 47 | put( key, values ); 48 | } 49 | values.add( value ); 50 | return values; 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | public MappedSet clone() { 55 | return (MappedSet)super.clone(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/jruby4max/util/TextBlock.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | /* 4 | Copyright (c) 2008, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | 33 | /** 34 | * A singleton Map of Strings. Used to indirectly pass long messages in Max. 35 | * 36 | * @author Adam Murray (adam@compusition.com) 37 | */ 38 | public class TextBlock { 39 | 40 | private static Map textMap = new HashMap(); 41 | 42 | private TextBlock() { 43 | } 44 | 45 | public synchronized static void set( String name, String text ) { 46 | textMap.put( name, text ); 47 | } 48 | 49 | public synchronized static String get( String name ) { 50 | return textMap.get( name ); 51 | } 52 | 53 | public synchronized static void remove( String name ) { 54 | textMap.remove( name ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/jruby4max/util/TextViewer.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | /* 4 | Copyright (c) 2008-2009, Adam Murray (adam@compusition.com). All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, 10 | this list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | */ 29 | 30 | import javax.swing.*; 31 | import java.awt.*; 32 | 33 | /** 34 | * A Swing-based popup window for viewing text. 35 | * 36 | * @author Adam Murray (adam@compusition.com) 37 | */ 38 | public class TextViewer { 39 | 40 | private JFrame frame; 41 | private JTextArea textArea; 42 | private boolean packed = false; 43 | private int width; 44 | private int height; 45 | Dimension dim; 46 | 47 | public TextViewer( String name ) { 48 | frame = new JFrame( name ); 49 | frame.setDefaultCloseOperation( JFrame.HIDE_ON_CLOSE ); 50 | width = 600; 51 | height = 450; 52 | 53 | textArea = new JTextArea(); 54 | textArea.setEditable( false ); 55 | textArea.setFont( new Font( "Monospaced", Font.PLAIN, 11 ) ); 56 | 57 | JScrollPane scroller = new JScrollPane( textArea ); 58 | scroller.setPreferredSize( new Dimension( width, height ) ); 59 | 60 | frame.add( scroller, BorderLayout.CENTER ); 61 | } 62 | 63 | public void show() { 64 | SwingUtilities.invokeLater( new Runnable() { 65 | public void run() { 66 | if( !packed ) { 67 | frame.pack(); 68 | packed = true; 69 | } 70 | frame.setVisible( true ); 71 | } 72 | } ); 73 | } 74 | 75 | public void hide() { 76 | frame.setVisible( false ); 77 | } 78 | 79 | public void setText( String text ) { 80 | textArea.setText( text ); 81 | } 82 | 83 | private int WINDOW_PADDING = 10; 84 | 85 | public void setCenter( int x, int y ) { 86 | x -= width / 2; 87 | y -= height / 2; 88 | 89 | if( x < WINDOW_PADDING ) x = WINDOW_PADDING; 90 | if( y < WINDOW_PADDING ) y = WINDOW_PADDING; 91 | 92 | Toolkit toolkit = Toolkit.getDefaultToolkit(); 93 | Dimension resolution = toolkit.getScreenSize(); 94 | int maxX = resolution.width - width - WINDOW_PADDING; 95 | int maxY = resolution.height - height - WINDOW_PADDING; 96 | if( x > maxX ) x = maxX; 97 | if( y > maxY ) y = maxY; 98 | 99 | frame.setLocation( x, y ); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/jruby4max/util/Utils.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | import com.cycling74.max.Atom; 4 | import com.cycling74.max.MaxPatcher; 5 | import com.cycling74.max.MaxSystem; 6 | import jruby4max.maxsupport.Atomizer; 7 | 8 | import java.io.*; 9 | import java.util.Collection; 10 | 11 | /* 12 | Copyright (c) 2008-2010, Adam Murray (adam@compusition.com). All rights reserved. 13 | 14 | Redistribution and use in source and binary forms, with or without 15 | modification, are permitted provided that the following conditions are met: 16 | 17 | 1. Redistributions of source code must retain the above copyright notice, 18 | this list of conditions and the following disclaimer. 19 | 20 | 2. Redistributions in binary form must reproduce the above copyright notice, 21 | this list of conditions and the following disclaimer in the documentation 22 | and/or other materials provided with the distribution. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 25 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 26 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 27 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 28 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 29 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 30 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 31 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 32 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 33 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 34 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35 | 36 | */ 37 | 38 | /** 39 | * Miscellaneous utility methods. 40 | * 41 | * @author Adam Murray (adam@compusition.com) 42 | */ 43 | public class Utils { 44 | 45 | private Utils() { 46 | } 47 | 48 | public static boolean equals( Object o1, Object o2 ) { 49 | if( o1 == null ) { 50 | return o2 == null; 51 | } 52 | else { 53 | return o1.equals( o2 ); 54 | } 55 | } 56 | 57 | /** 58 | * A safe toString() method that avoids NullPointerExceptions. 59 | * 60 | * @param obj the object to convert to a String 61 | * @return the String representation of the object 62 | */ 63 | public static String toString( Object obj ) { 64 | return (obj == null ? null : obj.toString()); 65 | } 66 | 67 | /** 68 | * Convert Atom[] to a space-separated String 69 | */ 70 | public static String toString( Atom[] atoms ) { 71 | String s = ""; 72 | if( atoms != null ) { 73 | for( int i = 0; i < atoms.length; i++ ) { 74 | if( i > 0 ) { 75 | s += " "; 76 | } 77 | s += atoms[i].toString(); 78 | } 79 | } 80 | return s; 81 | } 82 | 83 | public static Atom[] toAtoms( Collection objs ) { 84 | Atom[] atoms = new Atom[objs.size()]; 85 | int i = 0; 86 | for( Atomizer obj : objs ) { 87 | atoms[i] = obj.toAtom(); 88 | i++; 89 | } 90 | return atoms; 91 | } 92 | 93 | public static boolean isNumber( Atom atom ) { 94 | return atom.isInt() || atom.isFloat(); 95 | } 96 | 97 | public static String detokenize( Atom[] args ) { 98 | return detokenize( null, args ); 99 | } 100 | 101 | public static String detokenize( String msg, Atom[] args ) { 102 | StringBuilder input = new StringBuilder(); 103 | if( msg != null ) { 104 | input.append( detokenize( msg ) ).append( " " ); 105 | } 106 | for( int i = 0; i < args.length; i++ ) { 107 | if( i > 0 ) { 108 | input.append( " " ); 109 | } 110 | input.append( detokenize( args[i] ) ); 111 | } 112 | return input.toString(); 113 | } 114 | 115 | public static String detokenize( Atom atom ) { 116 | if( atom == null ) { 117 | return ""; 118 | } 119 | return detokenize( atom.toString() ); 120 | } 121 | 122 | public static String detokenize( String str ) { 123 | if( str.contains( " " ) ) { 124 | return '"' + str + '"'; 125 | } 126 | else { 127 | return str; 128 | } 129 | } 130 | 131 | /** 132 | * Locate a file. If no path is passed in a file dialog will open. If the path is just a filename, the Max search 133 | * path will be searched to locate the file. 134 | * 135 | * @param path - 136 | * a filename of a file on the max path, or an absolute path, or null (null opens file dialog) 137 | * @return a File object referencing an existing file, otherwise null 138 | */ 139 | public static File getFile( String path ) { 140 | return getFile( path, null ); 141 | } 142 | 143 | public static File getFile( String path, MaxPatcher patcher ) { 144 | return getFile( path, patcher, false ); 145 | } 146 | 147 | public static File getFile( String path, MaxPatcher patcher, boolean suppressError ) { 148 | return getFile( path, patcher, false, null ); 149 | } 150 | 151 | /** 152 | * Locate a file. If no path is passed in a file dialog will open. If the path is just a filename, the Max search 153 | * path will be searched to locate the file. If patcher is not null, then before searching the Max search path, 154 | * the path relative to the patcher will be searched. 155 | * 156 | * @param path - 157 | * a filename of a file on the max path, or an absolute path, or null (null opens file dialog) 158 | * @param patcher - 159 | * the patcher relative to which the path should be searched for first, before the Max search path 160 | * @param suppressError - 161 | * if false, when no file is found an error will be printed to syserr 162 | * @param optionalExtension - 163 | * if provided, and no file is found, the file is searched for again with the optionalExtension appended 164 | * @return a File object referencing an existing file, otherwise null 165 | */ 166 | public static File getFile( String path, MaxPatcher patcher, boolean suppressError, String optionalExtension ) { 167 | // If no file is provided, prompt the user to select one with a file dialog 168 | if( path == null || path.length() == 0 ) { 169 | path = MaxSystem.openDialog(); 170 | if( path == null ) { 171 | return null; // user canceled the file open dialog 172 | } 173 | // else a file was selected and we'll proceed to handle it like any path passed into this method 174 | } 175 | 176 | File file; 177 | // first see if it's an absolute path 178 | String location = MaxSystem.maxPathToNativePath( path ); 179 | if( location != null ) { 180 | file = new File( location ); 181 | if( file != null && file.isFile() ) { 182 | return file; 183 | } 184 | } 185 | 186 | // then see if we can find the file relative to the patcher 187 | if( patcher != null ) { 188 | File patcherFolder = new File( patcher.getPath() ); 189 | file = new File( patcherFolder, path ); 190 | if( file != null && file.isFile() ) { 191 | return file; 192 | } 193 | } 194 | 195 | // finally just try to locate the file via the Max search path 196 | location = MaxSystem.locateFile( path ); 197 | if( location != null ) { 198 | file = new File( location ); 199 | if( file != null && file.isFile() ) { 200 | return file; 201 | } 202 | } 203 | // if we make it here, NOTHING WAS FOUND 204 | 205 | // if an optional extension was provided, then look again with that appended 206 | if( optionalExtension != null && !path.endsWith( optionalExtension ) ) { 207 | file = getFile( path + optionalExtension, patcher, true, null ); 208 | if( file != null ) { 209 | return file; 210 | } 211 | } 212 | 213 | // and if that didn't work either, it's time for an error (unless suppressed): 214 | if( !suppressError ) { 215 | System.err.println( "File not found: " + path ); 216 | } 217 | return null; 218 | } 219 | 220 | public static String getFileAsString( String path ) { 221 | return getFileAsString( getFile( path ) ); 222 | } 223 | 224 | public static String getFileAsString( File file ) { 225 | if( file == null || !file.exists() ) { 226 | return null; 227 | } 228 | try { 229 | return getReaderAsString( new FileReader( file ) ); 230 | } catch(IOException e) { 231 | System.err.println( e.getMessage() ); 232 | return null; 233 | } 234 | } 235 | 236 | public static String getInputStreamAsString( InputStream in ) { 237 | return getReaderAsString( new InputStreamReader( in ) ); 238 | } 239 | 240 | public static String getReaderAsString( Reader r ) { 241 | StringBuilder text = new StringBuilder( 5000 ); 242 | BufferedReader reader = null; 243 | try { 244 | reader = new BufferedReader( r ); 245 | char[] buf = new char[1024]; 246 | int charsRead = 0; 247 | while( (charsRead = reader.read( buf )) != -1 ) { 248 | text.append( buf, 0, charsRead ); 249 | } 250 | return text.toString(); 251 | } catch(IOException e) { 252 | System.err.println( e.getMessage() ); 253 | return null; 254 | } finally { 255 | if( reader != null ) { 256 | try { 257 | reader.close(); 258 | } catch(IOException e) { 259 | System.err.println( e.getMessage() ); 260 | } 261 | } 262 | } 263 | } 264 | 265 | /** 266 | * @return a String representation of the provided Throwable 267 | */ 268 | public static String getStackTrace( Throwable t ) { 269 | Writer stw = new StringWriter(); 270 | t.printStackTrace( new PrintWriter( stw ) ); 271 | return stw.toString(); 272 | } 273 | 274 | /** 275 | * @return true if the patcher has been saved to a file, false otherwise 276 | */ 277 | public static boolean isPatcherSaved( MaxPatcher patcher ) { 278 | if( patcher != null ) { 279 | String filePath = patcher.getFilePath(); 280 | if( filePath != null ) { 281 | filePath = filePath.toLowerCase(); 282 | if( filePath.endsWith( ".maxpat" ) || filePath.endsWith( ".maxhelp" ) || filePath.endsWith( ".json" ) 283 | || filePath.endsWith( ".amxd" ) ) { 284 | return true; 285 | } 286 | // Otherwise this is probably something like "/" because the patcher is not saved. 287 | // I'm afraid to blacklist against "/" instead of the above check, because of potential cross platform issues. 288 | // Things would be so much easier if the filePath was null in this case... 289 | } 290 | } 291 | return false; 292 | } 293 | 294 | 295 | public static String pathToContainingFolder(String filepath) { 296 | return pathToContainingFolder(new File(filepath)); 297 | } 298 | 299 | public static String pathToContainingFolder(File file) { 300 | File folder = file.getParentFile(); 301 | if (folder != null) { 302 | String folderPath; 303 | try { 304 | folderPath = folder.getCanonicalPath(); 305 | } catch (IOException e) { 306 | System.err.println(e); 307 | folderPath = folder.getAbsolutePath(); 308 | } 309 | return folderPath; 310 | } 311 | return null; 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /test/jruby4max/rubysupport/ScriptEvaluatorManagerTest.java: -------------------------------------------------------------------------------- 1 | package jruby4max.rubysupport; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class ScriptEvaluatorManagerTest { 10 | 11 | @Test 12 | public void ensureIdAvailable_should_do_nothing_when_the_id_is_available() { 13 | Map idMap = new HashMap(); 14 | Object o = new Object(); 15 | ScriptEvaluatorManager.ensureIdAvailable(idMap, o, "id"); 16 | } 17 | 18 | @Test 19 | public void ensureIdAvailable_should_do_nothing_when_the_id_is_taken_by_the_current_object() { 20 | Map idMap = new HashMap(); 21 | Object o = new Object(); 22 | idMap.put("id", o); 23 | ScriptEvaluatorManager.ensureIdAvailable(idMap, o, "id"); 24 | } 25 | 26 | @Test 27 | public void ensureIdAvailable_should_throw_an_exception_with_an_available_id_when_the_id_is_taken() { 28 | Map idMap = new HashMap(); 29 | idMap.put("id", new Object()); 30 | Object o = new Object(); 31 | try { 32 | ScriptEvaluatorManager.ensureIdAvailable(idMap, o, "id"); 33 | } 34 | catch(IdInUseException e) { 35 | Assert.assertEquals("id[1]", e.getMessage()); 36 | } 37 | } 38 | 39 | @Test 40 | public void ensureIdAvailable_should_increment_suggested_id_index() { 41 | Map idMap = new HashMap(); 42 | idMap.put("id[1]", new Object()); 43 | Object o = new Object(); 44 | try { 45 | ScriptEvaluatorManager.ensureIdAvailable(idMap, o, "id[1]"); 46 | } 47 | catch(IdInUseException e) { 48 | Assert.assertEquals("id[2]", e.getMessage()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/jruby4max/util/MappedSetTest.java: -------------------------------------------------------------------------------- 1 | package jruby4max.util; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | public class MappedSetTest { 12 | 13 | private MappedSet subject; 14 | 15 | @Before 16 | public void before() { 17 | subject = new MappedSet(); 18 | } 19 | 20 | @Test 21 | public void addValue_should_prevent_duplicates() { 22 | subject.addValue("key", 5); 23 | subject.addValue("key", 5); 24 | Object[] value = subject.get("key").toArray(); 25 | Assert.assertEquals(1, value.length); 26 | Assert.assertEquals(5, value[0]); 27 | } 28 | 29 | @Test 30 | public void addValue_should_maintain_insertion_order() { 31 | subject.addValue("key", 1); 32 | subject.addValue("key", 2); 33 | long counter = 1; 34 | for(Integer value : subject.get("key")) { 35 | Assert.assertEquals(counter, (long)value); 36 | counter++; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/patches/loadbang_test.maxpat: -------------------------------------------------------------------------------- 1 | { 2 | "patcher" : { 3 | "fileversion" : 1, 4 | "rect" : [ 25.0, 69.0, 410.0, 330.0 ], 5 | "bglocked" : 0, 6 | "defrect" : [ 25.0, 69.0, 410.0, 330.0 ], 7 | "openrect" : [ 0.0, 0.0, 0.0, 0.0 ], 8 | "openinpresentation" : 0, 9 | "default_fontsize" : 12.0, 10 | "default_fontface" : 0, 11 | "default_fontname" : "Arial", 12 | "gridonopen" : 0, 13 | "gridsize" : [ 15.0, 15.0 ], 14 | "gridsnaponopen" : 0, 15 | "toolbarvisible" : 1, 16 | "boxanimatetime" : 200, 17 | "imprint" : 0, 18 | "enablehscroll" : 1, 19 | "enablevscroll" : 1, 20 | "devicewidth" : 0.0, 21 | "boxes" : [ { 22 | "box" : { 23 | "fontname" : "Arial", 24 | "fontsize" : 12.0, 25 | "id" : "obj-3", 26 | "maxclass" : "newobj", 27 | "numinlets" : 1, 28 | "numoutlets" : 0, 29 | "patching_rect" : [ 59.0, 182.0, 113.0, 20.0 ], 30 | "text" : "print loadbang_test" 31 | } 32 | 33 | } 34 | , { 35 | "box" : { 36 | "fontname" : "Arial", 37 | "fontsize" : 12.0, 38 | "id" : "obj-11", 39 | "maxclass" : "message", 40 | "numinlets" : 2, 41 | "numoutlets" : 1, 42 | "outlettype" : [ "" ], 43 | "patching_rect" : [ 102.0, 57.0, 32.5, 18.0 ], 44 | "text" : "'bar'" 45 | } 46 | 47 | } 48 | , { 49 | "box" : { 50 | "fontname" : "Arial", 51 | "fontsize" : 12.0, 52 | "id" : "obj-10", 53 | "maxclass" : "message", 54 | "numinlets" : 2, 55 | "numoutlets" : 1, 56 | "outlettype" : [ "" ], 57 | "patching_rect" : [ 59.0, 57.0, 32.5, 18.0 ], 58 | "text" : "'foo'" 59 | } 60 | 61 | } 62 | , { 63 | "box" : { 64 | "fontname" : "Arial", 65 | "fontsize" : 12.0, 66 | "id" : "obj-8", 67 | "maxclass" : "newobj", 68 | "numinlets" : 1, 69 | "numoutlets" : 1, 70 | "outlettype" : [ "" ], 71 | "patching_rect" : [ 59.0, 105.0, 107.0, 20.0 ], 72 | "text" : "prepend eval out0" 73 | } 74 | 75 | } 76 | , { 77 | "box" : { 78 | "fontname" : "Arial", 79 | "fontsize" : 12.0, 80 | "id" : "obj-7", 81 | "linecount" : 2, 82 | "maxclass" : "comment", 83 | "numinlets" : 1, 84 | "numoutlets" : 0, 85 | "patching_rect" : [ 59.0, 278.0, 237.0, 34.0 ], 86 | "text" : "This should set a message, and not print an error in the Max window" 87 | } 88 | 89 | } 90 | , { 91 | "box" : { 92 | "fontname" : "Arial", 93 | "fontsize" : 12.0, 94 | "id" : "obj-5", 95 | "maxclass" : "newobj", 96 | "numinlets" : 1, 97 | "numoutlets" : 1, 98 | "outlettype" : [ "" ], 99 | "patching_rect" : [ 59.0, 212.0, 81.0, 20.0 ], 100 | "text" : "loadmess set" 101 | } 102 | 103 | } 104 | , { 105 | "box" : { 106 | "fontname" : "Arial", 107 | "fontsize" : 12.0, 108 | "id" : "obj-4", 109 | "maxclass" : "message", 110 | "numinlets" : 2, 111 | "numoutlets" : 1, 112 | "outlettype" : [ "" ], 113 | "patching_rect" : [ 59.0, 247.0, 226.0, 18.0 ] 114 | } 115 | 116 | } 117 | , { 118 | "box" : { 119 | "fontname" : "Arial", 120 | "fontsize" : 12.0, 121 | "id" : "obj-2", 122 | "maxclass" : "newobj", 123 | "numinlets" : 1, 124 | "numoutlets" : 1, 125 | "outlettype" : [ "bang" ], 126 | "patching_rect" : [ 59.0, 18.0, 60.0, 20.0 ], 127 | "text" : "loadbang" 128 | } 129 | 130 | } 131 | , { 132 | "box" : { 133 | "fontname" : "Arial", 134 | "fontsize" : 12.0, 135 | "id" : "obj-1", 136 | "maxclass" : "newobj", 137 | "numinlets" : 1, 138 | "numoutlets" : 1, 139 | "outlettype" : [ "" ], 140 | "patching_rect" : [ 59.0, 137.0, 59.0, 20.0 ], 141 | "text" : "mxj jruby" 142 | } 143 | 144 | } 145 | ], 146 | "lines" : [ { 147 | "patchline" : { 148 | "destination" : [ "obj-3", 0 ], 149 | "hidden" : 0, 150 | "midpoints" : [ ], 151 | "source" : [ "obj-1", 0 ] 152 | } 153 | 154 | } 155 | , { 156 | "patchline" : { 157 | "destination" : [ "obj-4", 1 ], 158 | "hidden" : 0, 159 | "midpoints" : [ 68.5, 171.5, 275.5, 171.5 ], 160 | "source" : [ "obj-1", 0 ] 161 | } 162 | 163 | } 164 | , { 165 | "patchline" : { 166 | "destination" : [ "obj-8", 0 ], 167 | "hidden" : 0, 168 | "midpoints" : [ ], 169 | "source" : [ "obj-10", 0 ] 170 | } 171 | 172 | } 173 | , { 174 | "patchline" : { 175 | "destination" : [ "obj-8", 0 ], 176 | "hidden" : 0, 177 | "midpoints" : [ ], 178 | "source" : [ "obj-11", 0 ] 179 | } 180 | 181 | } 182 | , { 183 | "patchline" : { 184 | "destination" : [ "obj-10", 0 ], 185 | "hidden" : 0, 186 | "midpoints" : [ ], 187 | "source" : [ "obj-2", 0 ] 188 | } 189 | 190 | } 191 | , { 192 | "patchline" : { 193 | "destination" : [ "obj-11", 0 ], 194 | "hidden" : 0, 195 | "midpoints" : [ ], 196 | "source" : [ "obj-2", 0 ] 197 | } 198 | 199 | } 200 | , { 201 | "patchline" : { 202 | "destination" : [ "obj-4", 0 ], 203 | "hidden" : 0, 204 | "midpoints" : [ ], 205 | "source" : [ "obj-5", 0 ] 206 | } 207 | 208 | } 209 | , { 210 | "patchline" : { 211 | "destination" : [ "obj-1", 0 ], 212 | "hidden" : 0, 213 | "midpoints" : [ ], 214 | "source" : [ "obj-8", 0 ] 215 | } 216 | 217 | } 218 | ] 219 | } 220 | 221 | } 222 | --------------------------------------------------------------------------------