diff options
author | Hans-Christoph Steiner <eighthave@users.sourceforge.net> | 2008-03-22 02:15:12 +0000 |
---|---|---|
committer | Hans-Christoph Steiner <eighthave@users.sourceforge.net> | 2008-03-22 02:15:12 +0000 |
commit | a764e59e1d3a8e330f0d484fdb26b35ca3f0b2e4 (patch) | |
tree | c4ecadccdecf2809b99c0da0545f255a6ad25bb5 /src/java/com |
bringing pdj-0.8.3 into the main branchsvn2git-root
svn path=/trunk/externals/loaders/pdj/; revision=9621
Diffstat (limited to 'src/java/com')
38 files changed, 4305 insertions, 0 deletions
diff --git a/src/java/com/cycling74/io/ErrorStream.java b/src/java/com/cycling74/io/ErrorStream.java new file mode 100644 index 0000000..612bf3f --- /dev/null +++ b/src/java/com/cycling74/io/ErrorStream.java @@ -0,0 +1,10 @@ +package com.cycling74.io; + +import com.e1.pdj.*; +import java.io.*; + +public class ErrorStream extends PrintStream { + public ErrorStream() { + super(PDJSystem.err, true); + } +} diff --git a/src/java/com/cycling74/io/PostStream.java b/src/java/com/cycling74/io/PostStream.java new file mode 100644 index 0000000..5d7a58e --- /dev/null +++ b/src/java/com/cycling74/io/PostStream.java @@ -0,0 +1,10 @@ +package com.cycling74.io; + +import com.e1.pdj.*; +import java.io.*; + +public class PostStream extends PrintStream { + public PostStream() { + super(PDJSystem.out, true); + } +} diff --git a/src/java/com/cycling74/max/Atom.java b/src/java/com/cycling74/max/Atom.java new file mode 100644 index 0000000..c1949df --- /dev/null +++ b/src/java/com/cycling74/max/Atom.java @@ -0,0 +1,715 @@ +package com.cycling74.max; + +import java.io.*; +import java.util.*; + +/** + * PD element that is used in message or arguments. It can contains a + * float, a int (always map to a float in pd) or a string. + */ +public abstract class Atom implements Comparable, Serializable { + + /** + * Empty array to use with the API when theres is no arguments. + */ + public static final Atom[] emptyArray = new Atom[] {}; + + int type; + + Atom(int type) { + this.type = type; + } + + // Atom factories + /////////////////////////////////////////////////////////////////// + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(int value) { + return new AtomFloat(value); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(int value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;i<value.length;i++) { + ret[i] = newAtom(value[i]); + } + return ret; + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(long value) { + return new AtomFloat(value); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(long value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;i<value.length;i++) { + ret[i] = newAtom(value[i]); + } + return ret; + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(short value) { + return new AtomFloat(value); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(short value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;i<value.length;i++) { + ret[i] = newAtom(value[i]); + } + return ret; + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(byte value) { + return new AtomFloat(value); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(byte value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;i<value.length;i++) { + ret[i] = newAtom(value[i]); + } + return ret; + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(char value) { + return new AtomString(String.valueOf(value)); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(char value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;i<value.length;i++) { + ret[i] = newAtom(value[i]); + } + return ret; + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(boolean value) { + return new AtomFloat( value ? 1:0 ); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(boolean value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;i<value.length;i++) { + ret[i] = newAtom(value[i]); + } + return ret; + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(float value) { + return new AtomFloat(value); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(float value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;i<value.length;i++) { + ret[i] = newAtom(value[i]); + } + return ret; + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(double value) { + return new AtomFloat((float) value); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(double value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;i<value.length;i++) { + ret[i] = newAtom(value[i]); + } + return ret; + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom newAtom(String value) { + return new AtomString(value); + } + + /** + * Creates a new atom with the specified type. + * @param value the value of the atom + * @return the new Atom + */ + public static Atom[] newAtom(String value[]) { + Atom[] ret = new Atom[value.length]; + for(int i=0;i<value.length;i++) { + ret[i] = newAtom(value[i]); + } + return ret; + } + + /** + * Creates an array of atoms from string tokens. + * @param values the atoms value seperated by string + * @return the array of atoms created from the list + */ + public static Atom[] parse(String values) { + return parse(values, false); + } + + /** + * Creates an array of atoms from strings tokens. + * @param values the atoms value seperated by string + * @param skipfirst skip the first token + * @return the array of atoms created from the list + */ + public static Atom[] parse(String values, boolean skipfirst) { + ArrayList list = new ArrayList(); + StringTokenizer token = new StringTokenizer(values); + + if ( skipfirst && token.hasMoreTokens() ) { + token.nextToken(); + } + + while(token.hasMoreTokens()) { + list.add(newAtom(token.nextToken())); + } + + Iterator i = list.iterator(); + Atom[] ret = new Atom[list.size()]; + for(int idx=0; i.hasNext(); idx++) { + ret[idx] = (Atom) i.next(); + } + + return ret; + } + + // Atom methods + /////////////////////////////////////////////////////////////////// + + /** + * Get string value for this Atom. If the Atom does not contains a + * string it will throw an UnsupportedOperationException. + */ + public String getString() { + throw new UnsupportedOperationException("pdj: this atom is not a string"); + } + + /** + * Get int value for this Atom. If the Atom does not contains a + * int it will throw an UnsupportedOperationException. + */ + public int getInt() { + throw new UnsupportedOperationException("pdj: this atom is not a int"); + } + + /** + * Get float value for this Atom. If the Atom does not contains a + * float it will throw an UnsupportedOperationException. + */ + public float getFloat() { + throw new UnsupportedOperationException("pdj: this atom is not a float"); + } + + /** + * Returns the Object representation of the Atom. If it is an int or a + * float, it will return a Int/Float object. + * @return The java.lang.* representation of the object + */ + abstract Object toObject(); + + /** + * Returns the double value of this atom. + * @return the double value + */ + public double toDouble() { + return 0; + } + + /** + * Transform an array of Atom into an array of doubles. + * @param values the array of atoms + * @return array of doubles + */ + public static double[] toDouble(Atom[] values) { + double[] ret = new double[values.length]; + + for(int i=0;i<values.length;i++) { + ret[i] = values[i].toDouble(); + } + return ret; + } + + /** + * Returns the int value of this atom. + * @return the int value + */ + public int toInt() { + return 0; + } + + /** + * Transform an array of Atom into an array of ints. + * @param values the array of atoms + * @return array of ints + */ + public static int[] toInt(Atom[] values) { + int[] ret = new int[values.length]; + + for(int i=0;i<values.length;i++) { + ret[i] = values[i].toInt(); + } + return ret; + } + + + /** + * Returns the char value of this atom. + * @return the char value + */ + public char toChar() { + return 0; + } + + /** + * Transform an array of Atom into an array of chars. + * @param values the array of atoms + * @return array of chars + */ + public static char[] toChar(Atom[] values) { + char[] ret = new char[values.length]; + + for(int i=0;i<values.length;i++) { + ret[i] = values[i].toChar(); + } + return ret; + } + + + /** + * Returns the byte value of this atom. + * @return the byte value + */ + public byte toByte() { + return 0; + } + + /** + * Transform an array of Atom into an array of bytes. + * @param values the array of atoms + * @return array of bytes + */ + public static byte[] toByte(Atom[] values) { + byte[] ret = new byte[values.length]; + + for(int i=0;i<values.length;i++) { + ret[i] = values[i].toByte(); + } + return ret; + } + + /** + * Returns the long value of this atom. + * @return the long value + */ + public long toLong() { + return 0; + } + + /** + * Transform an array of Atom into an array of longs. + * @param values the array of atoms + * @return array of longs + */ + public static long[] toLong(Atom[] values) { + long[] ret = new long[values.length]; + + for(int i=0;i<values.length;i++) { + ret[i] = values[i].toLong(); + } + return ret; + } + + /** + * Returns the short value of this atom. + * @return the short value + */ + public short toShort() { + return 0; + } + + /** + * Transform an array of Atom into an array of shorts. + * @param values the array of atoms + * @return array of shorts + */ + public static short[] toShort(Atom[] values) { + short[] ret = new short[values.length]; + + for(int i=0;i<values.length;i++) { + ret[i] = values[i].toShort(); + } + return ret; + } + + /** + * Returns the float value of this atom. + * @return the float value + */ + public float toFloat() { + return 0; + } + + /** + * Transform an array of Atom into an array of floats. + * @param values the array of atoms + * @return array of floats + */ + public static float[] toFloat(Atom[] values) { + float[] ret = new float[values.length]; + + for(int i=0;i<values.length;i++) { + ret[i] = values[i].toFloat(); + } + return ret; + } + + /** + * Returns the boolean value of this atom. + * @return the boolean value + */ + public boolean toBoolean() { + return true; + } + + /** + * Transform an array of Atom into an array of booleans. + * @param values the array of atoms + * @return array of booleans + */ + public static boolean[] toBoolean(Atom[] values) { + boolean[] ret = new boolean[values.length]; + + for(int i=0;i<values.length;i++) { + ret[i] = values[i].toBoolean(); + } + return ret; + } + + /** + * Transform an array of Atom into an array of strings. + * @param array the array of atoms + * @return array of strings + */ + public static String[] toString(Atom[] array) { + String[] ret = new String[array.length]; + + for(int i=0;i<array.length;i++) { + ret[i] = array[i].toString(); + } + return ret; + } + + /** + * Returns true if the Atom has been created with a float. + * @return true if the Atom has been created with a float + */ + public boolean isFloat() { + return false; + } + + /** + * Returns true if the Atom has been created with a String. + * @return true if the Atom has been created with a String + */ + public boolean isString() { + return false; + } + + /** + * Returns true if the Atom has been created with a int. + * @return true if the Atom has been created with a int + */ + public boolean isInt() { + return false; + } + + /** + * Returns the array of Atom into one string. Atom elements are + * seperated by spaces. + * @param array the array of atom + * @return one string representation of the atom + */ + public static String toOneString(Atom[] array) { + StringBuffer sb = new StringBuffer(); + boolean appendSpace = false; + + for(int i=0;i<array.length;i++) { + if ( !appendSpace ) { + sb.append(' '); + appendSpace = true; + } + sb.append(array[i].toString()); + } + + return sb.toString(); + } + + /** + * Returns true if the instance has the same value of the object + * <code>object</code>. Similar to <code>"ok".equals("ok");</code> + */ + public abstract boolean equals(Object object); + + /** + * Returns the hashCode representation for this Atom. If it is an float, + * the float into bit value is return and if it is an String, the hashcode + * value of the string is returned. + */ + public abstract int hashCode(); + + // Array utility classes + /////////////////////////////////////////////////////////////////// + + /** + * Returns the index of the first atom that is found in the list. + * @param org the atom to find + * @param list the list of atom to search + * @return the index of the atom found. -1 if not found. + */ + public static int isIn(Atom org, Atom[] list) { + return isIn(org, list, 0, list.length-1); + } + + /** + * Returns the index of the first atom that is found in the list. + * @param org the atom to find + * @param list the list of atom to search + * @param from the start index to check + * @param to the last index to check + * @return the index of the atom found. -1 if not found. + */ + public static int isIn(Atom org, Atom[] list, int from, int to) { + for(int i=from;i<to+1;i++) { + if ( list[i].equals(org) ) + return i; + } + return -1; + } + + /** + * Remove at index <code>from</code> to index <code>to</code>. + * @param list the list to strip + * @param from the start index + * @param to the last index + * @return the stripped array + */ + public static Atom[] removeSome(Atom[] list, int from, int to) { + if ( from == 0 && from == list.length - 1 ) + return new Atom[] {}; + + Atom[] ret = new Atom[list.length - (to-from+1)]; + System.arraycopy(list, 0, ret, 0, from); + System.arraycopy(list, to + 1, ret, from, list.length - to - 1); + + return ret; + } + + /** + * Removes one atom in the list. + * @param list the list to strip + * @param i the index of the atom to remove + * @return the stripped array + */ + public static Atom[] removeOne(Atom[] list, int i) { + return removeSome(list, i, i); + } + + /** + * Removes the first atom in the list. + * @param list the list to strip + * @return the stripped array + */ + public static Atom[] removeFirst(Atom[] list) { + return removeFirst(list, 1); + } + + /** + * Removes the first <code>howmany</code> atoms in the list. + * @param list the list to strip + * @param howmany how many element to remove + * @return the stripped array + */ + public static Atom[] removeFirst(Atom[] list, int howmany) { + return removeSome(list, 0, howmany-1); + } + + /** + * Remove the last atom in the list. + * @param list the list to strip + * @return the stripped array + */ + public static Atom[] removeLast(Atom[] list) { + return removeLast(list, 1); + } + + /** + * Removes the last <code>howmany</code> atoms in the list. + * @param list the list to strip + * @param howmany how many element to remove + * @return the stripped array + */ + public static Atom[] removeLast(Atom[] list, int howmany) { + return removeSome(list, list.length-howmany, list.length-1); + } + + /** + * Reverses the element content; the first element is the last and so on. + * @param list the list to reverse + * @return the reversed list + */ + public static Atom[] reverse(Atom[] list) { + Atom[] ret = new Atom[list.length]; + int last = list.length - 1; + for(int i=0;i<list.length;i++) { + ret[last-i] = list[i]; + } + return ret; + } + + /** + * Rotates array content x number of times. + * @param list the list to rotate + * @param nbTimes the number of time that the array must be rotated + * @return the rotated list + */ + public static Atom[] rotate(Atom[] list, int nbTimes) { + Atom ret[] = new Atom[list.length]; + + for(int i=0;i<list.length;i++) { + ret[(i+nbTimes) % list.length] = list[i]; + } + + return ret; + } + + /** + * Don't know what this does. Max/MSP says: it does an union. If + * you want me to support this, send me an email and what it does. + * @param first + * @param second + * @return nothing yet + * @throws UnsupportedOperationException + */ + public static Atom[] union(Atom[] first, Atom[] second) throws UnsupportedOperationException { + throw new UnsupportedOperationException("not implemented."); + } + + /** + * Don't know what this does. Max/MSP says: it does an intersection. + * If you want me to support this, send me an email and what it does. + * @param first + * @param second + * @return nothing yet + * @throws UnsupportedOperationException + */ + public static Atom[] intersection(Atom[] first, Atom[] second) throws UnsupportedOperationException { + throw new UnsupportedOperationException("not implemented."); + } + + /** + * Used to return a string representation of the list with atom type. + * @param values the array of atoms + * @return the string representation of the array + */ + public static String toDebugString(Atom[] values) { + StringBuffer sb = new StringBuffer(); + + sb.append("Atom["); + sb.append(values.length); + sb.append("]="); + for(int i=0;i<values.length;i++) { + sb.append('{'); + sb.append(values[i].toString()); + if ( values[i] instanceof AtomString ) + sb.append(":S}"); + else + sb.append(":F}"); + } + + return sb.toString(); + } +} diff --git a/src/java/com/cycling74/max/AtomFloat.java b/src/java/com/cycling74/max/AtomFloat.java new file mode 100644 index 0000000..ef763f8 --- /dev/null +++ b/src/java/com/cycling74/max/AtomFloat.java @@ -0,0 +1,96 @@ +package com.cycling74.max; + +class AtomFloat extends Atom { + private static final long serialVersionUID = 6497612039215711052L; + private float value; + + AtomFloat(float value) { + super(DataTypes.FLOAT); + this.value = value; + } + + public int compareTo(Object obj) { + if ( obj instanceof AtomFloat ) { + AtomFloat f = (AtomFloat) obj; + if ( f.getFloat() <= getFloat() ) + return 1; + else + return -1; + } + + if ( obj instanceof AtomString ) { + return 1; + } + + throw new ClassCastException(); + } + + public boolean isFloat() { + return true; + } + + public boolean isInt() { + return true; + } + + public Object toObject() { + return new Float(value); + } + + public int toInt() { + return (int) value; + } + + public double toDouble() { + return value; + } + + public long toLong() { + return (long) value; + } + + public float toFloat() { + return value; + } + + public short toShort() { + return (short) value; + } + + public byte toByte() { + return (byte) value; + } + + public char toChar() { + return (char) value; + } + + public String toString() { + return new Float(value).toString(); + } + + public boolean toBoolean() { + return value != 0; + } + + public float getFloat() { + return value; + } + + public int getInt() { + return (int) value; + } + + public boolean equals(Object comp) { + if ( !(comp instanceof AtomFloat) ) { + return false; + } + AtomFloat test = (AtomFloat) comp; + + return test.value == value; + } + + public int hashCode() { + return new Float(value).hashCode(); + } +} diff --git a/src/java/com/cycling74/max/AtomString.java b/src/java/com/cycling74/max/AtomString.java new file mode 100644 index 0000000..c4135af --- /dev/null +++ b/src/java/com/cycling74/max/AtomString.java @@ -0,0 +1,63 @@ +package com.cycling74.max; + +class AtomString extends Atom { + private static final long serialVersionUID = 1738861344247036680L; + private String value; + + AtomString(String value) { + super(DataTypes.MESSAGE); + this.value = value; + } + + public int compareTo(Object obj) { + if ( obj instanceof AtomString ) { + AtomString s = (AtomString) obj; + return s.getString().compareTo(getString()); + } + if ( obj instanceof AtomFloat ) { + return -1; + } + throw new ClassCastException(); + } + + public boolean isString() { + return true; + } + + public Object toObject() { + return value; + } + + public byte toByte() { + return (byte) value.charAt(0); + } + + public char toChar() { + return value.charAt(0); + } + + public String toString() { + return value; + } + + public boolean toBoolean() { + return !value.equals("false"); + } + + public String getString() { + return value; + } + + public boolean equals(Object comp) { + if ( !(comp instanceof AtomString) ) { + return false; + } + AtomString test = (AtomString) comp; + + return test.value.equals(value); + } + + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/java/com/cycling74/max/Attribute.java b/src/java/com/cycling74/max/Attribute.java new file mode 100644 index 0000000..2e5cccf --- /dev/null +++ b/src/java/com/cycling74/max/Attribute.java @@ -0,0 +1,463 @@ +package com.cycling74.max; + +import java.lang.reflect.*; +import com.e1.pdj.PDJError; + +class Attribute { + boolean readOnly = false; + + // the method setter and getter if specified + Method setter; + Method getter; + + // the field to update if no setter or getter is specified + Field field; + + // attribute type + char type; + + // the owner of the field or method + Object obj; + + // tells if the arguments are a array + boolean isArray; + + Attribute(Object obj, String name, String setter_name, String getter_name) { + char typeCheck = 0; + + try { + this.obj = obj; + if ( getter == null ) { + field = obj.getClass().getDeclaredField(name); + field.setAccessible(true); + type = mapType(field.getType()); + if ( type == '-' ) { + throw new PDJError("Field: " + name + " is a unsupported type"); + } + isArray = Character.isLowerCase(type); + } else { + getter = obj.getClass().getDeclaredMethod(getter_name, null); + type = mapType(getter.getReturnType()); + if ( type == '-' ) { + throw new PDJError("Method: " + getter_name + " returns a unsupported type"); + } + isArray = Character.isLowerCase(type); + } + + if ( setter == null ) { + field = obj.getClass().getDeclaredField(name); + field.setAccessible(true); + typeCheck = mapType(field.getType()); + } else { + Method methods[] = obj.getClass().getMethods(); + + for(int i=0;i<methods.length;i++) { + if ( methods[i].getName().equals(setter_name) ) { + Class c[] = methods[i].getParameterTypes(); + if ( c.length > 1 ) + throw new PDJError("Method: " + setter_name + " has too much parameters to be a setter"); + typeCheck = mapType(c[0]); + setter = methods[i]; + break; + } + } + if ( typeCheck == 0 ) { + throw new NoSuchMethodException(setter_name); + } + } + } catch ( Exception e ) { + throw new PDJError(e); + } + + if ( typeCheck != type ) { + throw new PDJError("Object type for setter and getter is not the same"); + } + + } + + private char mapType(Class clz) { + if ( clz == Integer.TYPE ) + return 'i'; + if ( clz == int[].class ) + return 'I'; + if ( clz == Float.TYPE ) + return 'f'; + if ( clz == float[].class ) + return 'F'; + if ( clz == Double.TYPE ) + return 'd'; + if ( clz == double[].class ) + return 'D'; + if ( clz == Boolean.TYPE ) + return 'z'; + if ( clz == Boolean[].class ) + return 'Z'; + if ( clz == Byte.TYPE ) + return 'b'; + if ( clz == byte[].class ) + return 'B'; + if ( clz == Character.TYPE ) + return 'c'; + if ( clz == char[].class ) + return 'C'; + if ( clz == Short.TYPE ) + return 's'; + if ( clz == short[].class ) + return 'S'; + if ( clz == String.class ) + return 'g'; + if ( clz == String[].class ) + return 'G'; + if ( clz == Atom.class ) + return 'a'; + if ( clz == Atom[].class ) + return 'A'; + return '-'; + } + + private void fieldSetter(Atom[] value) throws Exception { + Object work; + int i; + + switch ( type ) { + case 'z' : + field.setBoolean(obj, value[0].toBoolean()); + break; + case 'Z' : + work = field.get(obj); + for (i=0;i<value.length;i++) + Array.setBoolean(work, i, value[i].toBoolean()); + break; + case 'f' : + field.setFloat(obj, value[0].toFloat()); + break; + case 'F' : + work = field.get(obj); + for (i=0;i<value.length;i++) + Array.setFloat(work, i, value[i].toFloat()); + break; + case 'i' : + field.setInt(obj, value[0].toInt()); + break; + case 'I' : + work = field.get(obj); + for (i=0;i<value.length;i++) + Array.setInt(work, i, value[i].toInt()); + break; + case 'd' : + field.setDouble(obj, value[0].toDouble()); + break; + case 'D' : + work = field.get(obj); + for (i=0;i<value.length;i++) + Array.setDouble(work, i, value[i].toDouble()); + break; + case 'b' : + field.setByte(obj, value[0].toByte()); + break; + case 'B' : + work = field.get(obj); + for (i=0;i<value.length;i++) + Array.setByte(work, i, value[i].toByte()); + break; + case 'c' : + field.setChar(obj, value[0].toChar()); + break; + case 'C' : + work = field.get(obj); + for (i=0;i<value.length;i++) + Array.setChar(work, i, value[i].toChar()); + break; + case 's' : + field.setShort(obj, value[0].toShort()); + break; + case 'S' : + work = field.get(obj); + for (i=0;i<value.length;i++) + Array.setShort(work, i, value[i].toShort()); + break; + case 'g' : + field.set(obj, value.toString()); + break; + case 'G' : + work = field.get(obj); + for (i=0;i<value.length;i++) + Array.set(work, i, value[i].toString()); + break; + case 'a' : + field.set(obj, value[0]); + break; + case 'A' : + field.set(obj, value); + break; + default: + throw new PDJError("Unable to pass attribute value"); + } + } + + private Atom[] fieldGetter() throws Exception { + Object array = null; + Atom[] ret; + int i; + + if ( isArray ) { + ret = new Atom[1]; + } else { + array = field.get(obj); + ret = new Atom[Array.getLength(array)]; + } + + switch ( type ) { + case 'z' : + ret[0] = Atom.newAtom(field.getBoolean(obj)); + break; + case 'Z' : + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(Array.getBoolean(array, i)); + break; + case 'f' : + ret[0] = Atom.newAtom(field.getFloat(obj)); + break; + case 'F' : + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(Array.getFloat(array, i)); + break; + case 'i' : + ret[0] = Atom.newAtom(field.getInt(obj)); + break; + case 'I' : + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(Array.getInt(array, i)); + break; + case 'd' : + ret[0] = Atom.newAtom(field.getDouble(obj)); + break; + case 'D' : + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(Array.getDouble(array, i)); + break; + case 'b' : + ret[0] = Atom.newAtom(field.getByte(obj)); + break; + case 'B' : + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(Array.getByte(array, i)); + break; + case 'c' : + ret[0] = Atom.newAtom(field.getChar(obj)); + break; + case 'C' : + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(Array.getChar(array, i)); + break; + case 's' : + ret[0] = Atom.newAtom(field.getShort(obj)); + break; + case 'S' : + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(Array.getShort(array, i)); + break; + case 'g' : + ret[0] = Atom.newAtom((String) field.get(obj)); + break; + case 'G' : + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom((String) Array.get(array, i)); + break; + case 'a' : + ret[0] = (Atom) field.get(obj); + break; + case 'A' : + for (i=0;i<ret.length;i++) + ret[i] = (Atom) Array.get(array, i); + break; + default: + throw new PDJError("Unable to pass attribute value"); + } + + return ret; + } + + private Atom[] methodGetter() throws Exception { + Object array = null; + Atom[] ret; + int i; + + if ( isArray ) { + ret = new Atom[1]; + } else { + array = field.get(obj); + ret = new Atom[Array.getLength(array)]; + } + + switch ( type ) { + case 'z' : + ret[0] = Atom.newAtom(((Boolean)getter.invoke(obj, null)).booleanValue()); + break; + case 'Z' : + boolean tmpz[] = (boolean[]) getter.invoke(obj, null); + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(tmpz[i]); + break; + case 'f' : + ret[0] = Atom.newAtom(((Float)getter.invoke(obj, null)).floatValue()); + break; + case 'F' : + float tmpf[] = (float[]) getter.invoke(obj, null); + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(tmpf[i]); + break; + case 'i' : + ret[0] = Atom.newAtom(((Integer)getter.invoke(obj, null)).intValue()); + break; + case 'I' : + int tmpi[] = (int[]) getter.invoke(obj, null); + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(tmpi[i]); + break; + case 'd' : + ret[0] = Atom.newAtom(((Double)getter.invoke(obj, null)).doubleValue()); + break; + case 'D' : + double tmpd[] = (double[]) getter.invoke(obj, null); + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(tmpd[i]); + break; + case 'b' : + ret[0] = Atom.newAtom(((Byte)getter.invoke(obj, null)).byteValue()); + break; + case 'B' : + byte tmpb[] = (byte[]) getter.invoke(obj, null); + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(tmpb[i]); + break; + case 'c' : + ret[0] = Atom.newAtom(((Character)getter.invoke(obj, null)).charValue()); + break; + case 'C' : + char tmpc[] = (char[]) getter.invoke(obj, null); + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(tmpc[i]); + break; + case 's' : + ret[0] = Atom.newAtom(((Short)getter.invoke(obj, null)).shortValue()); + break; + case 'S' : + short tmps[] = (short[]) getter.invoke(obj, null); + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom(tmps[i]); + break; + case 'g' : + ret[0] = Atom.newAtom((String) getter.invoke(obj, null)); + break; + case 'G' : + for (i=0;i<ret.length;i++) + ret[i] = Atom.newAtom((String) Array.get(array, i)); + break; + case 'a' : + ret[0] = (Atom) getter.invoke(obj, null); + break; + case 'A' : + ret = (Atom []) getter.invoke(obj, null); + break; + default: + throw new PDJError("Unable to pass attribute value"); + } + + return ret; + } + + private Object setterCast(Atom values[]) throws Exception { + int i; + + switch ( type ) { + case 'z' : + return new Boolean(values[0].toBoolean()); + case 'Z' : + boolean tmpz[] = new boolean[values.length]; + for (i=0;i<values.length; i++) + tmpz[i] = values[i].toBoolean(); + return tmpz; + case 'f' : + return new Float(values[0].toFloat()); + case 'F' : + float tmpf[] = new float[values.length]; + for (i=0;i<values.length; i++) + tmpf[i] = values[i].toFloat(); + return tmpf; + case 'i' : + return new Integer(values[0].toInt()); + case 'I' : + int tmpi[] = new int[values.length]; + for (i=0;i<values.length; i++) + tmpi[i] = values[i].toInt(); + return tmpi; + case 'd' : + return new Double(values[0].toDouble()); + case 'D' : + double tmpd[] = new double[values.length]; + for (i=0;i<values.length; i++) + tmpd[i] = values[i].toDouble(); + return tmpd; + case 'b' : + return new Byte(values[0].toByte()); + case 'B' : + byte tmpb[] = new byte[values.length]; + for (i=0;i<values.length; i++) + tmpb[i] = values[i].toByte(); + return tmpb; + case 'c' : + return new Character(values[0].toChar()); + case 'C' : + char tmpc[] = new char[values.length]; + for (i=0;i<values.length; i++) + tmpc[i] = values[i].toChar(); + return tmpc; + case 's' : + return new Short(values[0].toShort()); + case 'S' : + short tmps[] = new short[values.length]; + for (i=0;i<values.length; i++) + tmps[i] = values[i].toShort(); + return tmps; + case 'g' : + return values[0].toString(); + case 'G' : + String tmpg[] = new String[values.length]; + for (i=0;i<values.length;i++) + tmpg[i] = values[0].toString(); + return tmpg; + case 'a' : + return values[0]; + case 'A' : + return values; + default: + throw new PDJError("Unable to pass attribute value"); + } + } + + Atom[] get() { + try { + if ( getter == null ) + return fieldGetter(); + return methodGetter(); + } catch ( Exception e ){ + throw new PDJError(e); + } + } + + void set(Atom values[]) { + if ( readOnly ) + throw new PDJError("Field is readonly"); + + try { + if ( setter == null ) + fieldSetter(values); + else + setter.invoke(obj, new Object[] { setterCast(values) }); + } catch (Exception e) { + throw new PDJError(e); + } + } + +} diff --git a/src/java/com/cycling74/max/Callback.java b/src/java/com/cycling74/max/Callback.java new file mode 100644 index 0000000..1eed30f --- /dev/null +++ b/src/java/com/cycling74/max/Callback.java @@ -0,0 +1,213 @@ +package com.cycling74.max; + +import java.lang.reflect.*; + +/** + * Used to transform a java method into an Executable object. This + * simplify the job of always implementing the executable + * interface on all your objects since you can define it dynamically + * with this class. + * <p><blockquote><pre> + * + * class Myclass { + * public void doit() { + * do_something_fun() + * } + * } + * + * ... + * + * Myclass myclass = new Myclass(); + * Executable e = new Callback(myclass, "doit"); + * MaxClock clock = new MaxClock(e); + * + * </pre></blockquote></p> + */ +public class Callback implements Executable { + private Method method; + private String methodName; + private Object obj; + private Object args[]; + + /** + * Will call method <i>methodName</i> with no argument by using execute() + * @param obj the object with the method + * @param methodName the name of the method + */ + public Callback(Object obj, String methodName) { + this(obj, methodName, null, null); + } + + /** + * Will call method <i>methodName</i> with a int by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param i the int value + */ + public Callback(Object obj, String methodName, int i) { + this(obj, methodName, new Object[] { new Integer(i) }, new Class[] { Integer.TYPE }); + } + + /** + * Will call method <i>methodName</i> with a float by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param f the float value + */ + public Callback(Object obj, String methodName, float f) { + this(obj, methodName, new Object[] { new Float(f) }, new Class[] { Float.TYPE }); + } + + /** + * Will call method <i>methodName</i> with a Stringt by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param str the string value + */ + public Callback(Object obj, String methodName, String str) { + this(obj, methodName, new Object[] { str }); + } + + /** + * Will call method <i>methodName</i> with a boolean by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param flag the boolean value + */ + public Callback(Object obj, String methodName, boolean flag) { + this(obj, methodName, new Object[] { flag ? Boolean.TRUE : Boolean.FALSE }); + } + + /** + * Will call method <i>methodName</i> with multiple arguments by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param params argument to pass to the method + */ + public Callback(Object obj, String methodName, Object params[]) { + this(obj, methodName, params, buildClasses(params)); + } + + /** + * Will call method <i>methodName</i> with multiple arguments (typed) by using execute() + * @param obj the object with the method + * @param methodName the name of the method + * @param params argument to pass to the method + * @param params_types the type of arguments + */ + public Callback(Object obj, String methodName, Object params[], Class params_types[]) { + try { + if ( params == null ) { + method = obj.getClass().getDeclaredMethod(methodName, null); + } else { + method = obj.getClass().getDeclaredMethod(methodName, params_types); + } + this.obj = obj; + this.methodName = methodName; + } catch ( NoSuchMethodException e ) { + MaxSystem.post("pdj: unable to find method: " + methodName + ", "+ e); + } + } + + private static Class[] buildClasses(Object params[]) { + Class clz[] = new Class[params.length]; + + for(int i=0;i<params.length;i++) { + clz[i] = params[i].getClass(); + } + + return clz; + } + + /** + * Execute the method with arguments specified at constructor + */ + public void execute() { + if ( obj == null ) { + throw new Error("pdj: this Callback has never been initialised"); + } + + try { + method.invoke(obj, args); + } catch (IllegalArgumentException e) { + MaxSystem.error("pdj: IllegalArgumentException:" + e); + } catch (IllegalAccessException e) { + MaxSystem.error("pdj: IllegalAccessException:" + e); + } catch (InvocationTargetException e) { + MaxSystem.error("pdj: InvocationTargetException:" + e); + } + } + + /** + * Returns the argument that will be used when the execute method + * will be invoke. + * @return the array of arguments + */ + public Object[] getArgs() { + return args; + } + + /** + * Returns the object used to issue the call + * @return the object to use with execute + */ + public Object getObject() { + return obj; + } + + /** + * Returns the Method that will be used with the execute call + * @return the method given by reflection + */ + public Method getMethod() { + return method; + } + + /** + * Returns the method name invoked on object with the execute call + * @return the method name + */ + public String getMethodName() { + return methodName; + } + + /** + * Sets int argument to method + * @param i int value + */ + public void setArgs(int i) { + args = new Object[] { new Integer(i) }; + } + + /** + * Sets float argument to method + * @param f float argument + */ + public void setArgs(float f) { + args = new Object[] { new Float(f) }; + } + + /** + * Sets String argument to method + * @param value int value + */ + public void setArgs(String value) { + args = new Object[] { value }; + } + + /** + * Sets boolean argument to method + * @param flag boolean value + */ + public void setArgs(boolean flag) { + args = new Object[] { flag ? Boolean.TRUE : Boolean.FALSE }; + } + + /** + * Set the argument for the execute call + * @param args the array object to pass to the method + */ + public void setArgs(Object args[]) { + this.args = (Object[]) args.clone(); + } +} diff --git a/src/java/com/cycling74/max/DataTypes.java b/src/java/com/cycling74/max/DataTypes.java new file mode 100644 index 0000000..db0f2a2 --- /dev/null +++ b/src/java/com/cycling74/max/DataTypes.java @@ -0,0 +1,14 @@ + +package com.cycling74.max; + +/** + * DataTypes values that can be used to defined an inlet or outlet. + */ +public class DataTypes { + public static int ALL = 15; + public static int ANYTHING = 15; + public static int FLOAT = 2; + public static int INT = 1; + public static int LIST = 4; + public static int MESSAGE = 8; +} diff --git a/src/java/com/cycling74/max/Executable.java b/src/java/com/cycling74/max/Executable.java new file mode 100644 index 0000000..c658d3f --- /dev/null +++ b/src/java/com/cycling74/max/Executable.java @@ -0,0 +1,14 @@ +package com.cycling74.max; + +/** + * Defines an executable object. An object that implements a + * Executable interface can later be used with a MaxClock or a + * MaxSystem.deferLow for example since it defines the + * execute method. + */ +public interface Executable { + /** + * The method to execute. + */ + public void execute(); +} diff --git a/src/java/com/cycling74/max/MaxClock.java b/src/java/com/cycling74/max/MaxClock.java new file mode 100644 index 0000000..5c5acbb --- /dev/null +++ b/src/java/com/cycling74/max/MaxClock.java @@ -0,0 +1,113 @@ +package com.cycling74.max; + +/** + * Used to delay the execution of a block of code. Java implementation + * of a pdclock. + * <p><blockquote><pre> + * + * import com.cycling74.max.*; + * + * class clocktest extends MaxObject implements Executable { + * MaxClock clock; + * float value; + * + * public clocktest() { + * clock = new MaxClock(this); + * } + * + * public void inlet(float f) { + * value = f; + * // ask to call execute after 250ms + * clock.delay(250); + * } + * + * // this is called after 250ms + * public void execute() { + * outlet(0, value) + * } + * } + * </pre></blockquote></p> + */ +public class MaxClock { + private Executable exec; + private long _clock_ptr; + + /** + * Creates a pdclock without an executable. + */ + public MaxClock() { + create_clock(); + } + + /** + * Creates a pdclock with an executable <code>e</code>. + * @param e the executable to execute when the clock will be triggerd. + */ + public MaxClock(Executable e) { + create_clock(); + exec = e; + } + + /** + * Creates a pdclock with a specific method on a object. + * @param o the object that holds the method + * @param methodName the name of the method to execute when the clock + * will be triggerd. + */ + public MaxClock(Object o, String methodName) { + create_clock(); + exec = new Callback(o, methodName); + } + + /** + * Returns the Executable for this clock. + * @return the Executable for this clock + */ + public Executable getExecutable() { + return exec; + } + + /** + * Set the Executable for this clock. + * @param e the Executable to call for this clock + */ + public void setExecutable(Executable e) { + exec = e; + } + + /** + * The method to override if no Executable is provided. + */ + public void tick() { + exec.execute(); + } + + protected void finalize() throws Throwable { + release(); + super.finalize(); + } + + /** + * Returns pure-data time in milliseconds. + * @return pure-data time in milliseconds + */ + public static native double getTime(); + + /** + * Time to wait until next tick. + * @param time in miliseconds + */ + public native void delay(double time); + + /** + * Release the clock from pure-data. The clock becomes unless afterwards. + */ + public native void release(); + + /** + * Cancels the last delay call. + */ + public native void unset(); + + private native void create_clock(); +} diff --git a/src/java/com/cycling74/max/MaxContext.java b/src/java/com/cycling74/max/MaxContext.java new file mode 100644 index 0000000..e5fae12 --- /dev/null +++ b/src/java/com/cycling74/max/MaxContext.java @@ -0,0 +1,21 @@ + +package com.cycling74.max; + +import java.util.Set; + +/** + * MaxContext holder. Does not do anything usefull on pdj, yet. + */ +public class MaxContext { + public Set getAllObject() { + return null; + } + + MaxObject getMaxObject(String name) { + return null; + } + + String getMxjVersion() { + return MaxSystem.MXJ_VERSION; + } +} diff --git a/src/java/com/cycling74/max/MaxObject.java b/src/java/com/cycling74/max/MaxObject.java new file mode 100644 index 0000000..3d07d49 --- /dev/null +++ b/src/java/com/cycling74/max/MaxObject.java @@ -0,0 +1,933 @@ +package com.cycling74.max; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; + +import com.e1.pdj.*; +import java.util.*; +import com.cycling74.msp.MSPObject; + +/** + * Main object to extend to use with pd. The name of this class will + * reflect the name of the pdj object when it is instanciated. + * <p>Here is a basic guideline for using the MaxObject:</p> + * <p><blockquote><pre> + * + * import com.cycling74.max.*; + * + * public class example extends MaxObject { + * + * // called when arguments are used in object creation + * public example(Atom args[]) { + * for(int i=0;i<args.length;i++) { + * post("args:" + args[i].toString()); + * } + * } + * + * // this method will be called when a float is sended to the object + * public inlet(float f) { + * post("hello float:" + f); + * outlet(0, f); + * } + * + * // this method will be called when symbol callme is sended to the object + * public callme(Atom args[]) { + * post("hello object:" + args[0]); + * outlet(0, args[0]); + * } + * + * // this method will be called when bang is sended to the object + * public bang() { + * post("hello bang"); + * outletBang(0); + * } + * } + * </pre></blockquote></p> + * <p>Compile this class by adding pdj.jar on the classpath and put the + * example.class in the <i>classes</i> directory found in your pdj home. + * You can also edit directly this class in the <i>classes</i> directory + * and pdj will try to compile it for you.</p> + */ +public class MaxObject { + /** + * Native C pdj object pointer. + */ + private long _pdobj_ptr; + + /** + * Native C inlet pointers. + */ + private long _outlets_ptr[]; + + /** + * Native C outlets pointers. + */ + private long _inlets_ptr[]; + + /** + * The last inlet that received a message. + */ + private int _activity_inlet; + private String name; + private HashMap attributes = new HashMap(); + private boolean toCreateInfoOutlet = true; + private MaxPatcher patch = new MaxPatcher(); + + // useless statics.... + /** + * Use this to declare that your object has no inlets. + */ + public static final int[] NO_INLETS = {}; + + /** + * Use this to declare that your object has no outlets. + */ + public static final int[] NO_OUTLETS = {}; + + /** + * Defined in the original MXJ API; don't know what it is used for... + */ + public static final String[] EMPTY_STRING_ARRAY = {}; + + /** + * Default constructor for MaxObject. You can add an constructor that + * supports Atom[] to read object creation arguments. It is not defined + * in this API since it does not to force a 'super()' call in all the + * extended class. + */ + protected MaxObject() { + synchronized (MaxObject.class) { + _pdobj_ptr = popPdjPointer(); + } + name = this.getClass().getName(); + patch.patchPath = getPatchPath(); + } + + /** + * Will show an error message in the pure-data console. + * @param message the string to show + */ + public static void error(String message) { + MaxSystem.error(message); + } + + /** + * Will show an info message in the pure-data console. + * @param message the string to show + */ + public static void post(String message) { + MaxSystem.post(message); + } + + /** + * Will show an error message in the pure-data console and crash + * pure-data. Do not use if you have no friend left. + * @param message the message to show when you make pd crash + */ + public static void ouch(String message) { + MaxSystem.ouch(message); + } + + /** + * Show the exeception in the pure-data console. + * @param t the exception it self + */ + public static void showException(Throwable t) { + t.printStackTrace(PDJSystem.err); + } + + /** + * Show the exception in the pure-data console with a message. + * @param message the message that comes with the exception + * @param t the exception it self + */ + public static void showException(String message, Throwable t) { + PDJSystem.err.println(message); + t.printStackTrace(PDJSystem.err); + } + + /** + * Returns the object name. + * @return the pdj object name + */ + public String getName() { + return name; + } + + /** + * Sets the object name. + * @param name pdj object name + */ + public void setName(String name) { + this.name = name; + } + + /** + * This method is called when the object is deleted by the user. + */ + public void notifyDeleted() { + } + + /** + * Bail will throw an exception upon object instanciation. This is usefull + * if you have a missing requirement and you want to cancel object creation. + * @param errormsg the error message to show + */ + protected static void bail(String errormsg) { + throw new PDJError(errormsg); + } + + /** + * Declare the inlets used by this object. + * @see com.cycling74.max.DataTypes + * @param types the type of message that this inlet will use. + */ + protected void declareInlets(int[] types) { + if ( _inlets_ptr != null ) { + throw new IllegalStateException(name + ": inlets already defined"); + } + _inlets_ptr = new long[types.length]; + + int pos = 0; + for(int i=0; i<types.length; i++) { + _inlets_ptr[pos++] = newInlet(types[i]); + } + } + + /** + * Declare the outlets used by this object. + * @see com.cycling74.max.DataTypes + * @param types the type of message that this inlet will use. + */ + protected void declareOutlets(int[] types) { + if ( _outlets_ptr != null ) { + throw new IllegalStateException(name + ": outlets already defined"); + } + _outlets_ptr = new long[types.length]; + + int pos = 0; + for(int i=0; i<types.length; i++) { + long ret = newOutlet(types[i]); + if ( ret != 0 ) + _outlets_ptr[pos++] = ret; + } + } + + /** + * Used to defined typed input and output for this object. Strings are + * used to define the type of inlet/outlet. For example, "ffi" will create + * a float/float/integer inlet/outlet. 'f' denotes a float, 'i' a int, 'm' + * a message, 'l' a list, 's' for a signal. Anything else will be considered + * as a outlet of type anything. <b>PD doesn't type all data yet, so every + * inlet/outlet are typed as anything; except for signals.</b> + * @param ins list of inlet to create + * @param outs list of outlet to create + */ + protected void declareTypedIO(String ins, String outs) { + if ( _inlets_ptr != null || _outlets_ptr != null ) { + throw new IllegalStateException(name + ": inlets/outles already defined."); + } + + int[] in_type = new int[ins.length()]; + int[] out_type = new int[outs.length()]; + int i; + + for (i=0;i<in_type.length;i++) { + if ( ins.charAt(i) == 's' ) + in_type[i] = MSPObject.SIGNAL; + else + in_type[i] = DataTypes.ANYTHING; + } + declareInlets(in_type); + + for (i=0;i<out_type.length;i++) { + if ( ins.charAt(i) == 's' ) + in_type[i] = MSPObject.SIGNAL; + else + in_type[i] = DataTypes.ANYTHING; + } + declareOutlets(out_type); + } + + /** + * Quickie method for declaring both inlet and outlet that will use + * any type of message (DataTypes.ANYTHING) + * @param in the number of inlet to create + * @param out the number of outlet to create + */ + protected void declareIO(int in, int out) { + if ( _inlets_ptr != null || _outlets_ptr != null ) { + throw new IllegalStateException(name + ": inlets/outlets already defined."); + } + + _inlets_ptr = new long[in]; + _outlets_ptr = new long[out]; + + for(int i=0; i<in; i++) { + _inlets_ptr[i] = newInlet(DataTypes.ANYTHING); + } + + for(int i=0; i<out; i++) { + _outlets_ptr[i] = newOutlet(DataTypes.ANYTHING); + } + } + + /** + * Creates a attribute with default setter and getter. The name used is + * actually a java field in the current class. Later, you can send 'a 10' + * and pdj will set 10 to your java field named 'a'. You can also + * use 'get a' that will return its value to the info outlet. The info + * outlet is the last outlet of your object. + * @param name name of the java field in this class to map. + */ + protected void declareAttribute(String name) { + Attribute attr = new Attribute(this, name, null, null); + attributes.put(name, attr); + } + + /** + * Creates a attribute with default getter no setter. The name used is + * actually a java field in the current class. + * @param name name of the java field in this class to map. + */ + protected void declareReadOnlyAttribute(String name) { + Attribute attr = new Attribute(this, name, null, null); + attr.readOnly = true; + attributes.put(name, attr); + } + + + void declareAttribute(String name, String getter, String setter) { + Attribute attr = new Attribute(this, name, getter, setter); + attributes.put(name, attr); + } + + void declareReadOnlyAttribute(String name, String getter) { + Attribute attr = new Attribute(this, name, getter, null); + attr.readOnly = true; + attributes.put(name, attr); + } + + /** + * Tells the constructor to create a info outlet. The info outlet is used + * by attributes when the get method is used. The return value of the 'get' + * will be return to this outlet. The info outlet is always the last + * outlet of the object. By default, the info outlet is always created. + * @param flag false to prevent the creation of the info outlet + */ + protected void createInfoOutlet(boolean flag) { + toCreateInfoOutlet = flag; + } + + /** + * Returns the type the inlet at index 'idx'. <i>Not fully implemented, + * returns always DataTypes.ANYTHING with pdj</i> + * @param idx the outlet position + * @return the DataType of the inlet + */ + public int getInletType(int idx) { + return DataTypes.ANYTHING; + } + + /** + * Returns the type of the outlet at index 'idx'. <i>Not fully implemented, + * returns always DataTypes.ANYTHING with pdj</i> + * @param idx the outlet position + * @return the DataType of the outlet + */ + public int getOutletType(int idx) { + return DataTypes.ANYTHING; + } + + /** + * Returns the number of inlets declared. + * @return the number of inlets used by this object + */ + public int getNumInlets() { + return _inlets_ptr.length; + } + + /** + * Returns the number of outlets declared. + * @return the number of outlets used by this object + */ + public int getNumOutlets() { + return _outlets_ptr.length; + } + + /** + * Called by PD when the current patch issue a loadbang message. + */ + protected void loadbang() { + } + + /** + * Returns the index of the inlet that has just received a message. + * @return the index of the inlet + */ + protected int getInlet() { + return _activity_inlet; + } + + /** + * Returns the index of the info outlet + */ + public int getInfoIdx() { + if ( !toCreateInfoOutlet ) { + return -1; + } + return _outlets_ptr.length-1; + } + + /** + * Sends a bang to outlet x. + * @param outlet the outlet number to use + * @return always true, since PD API returns void + */ + public final boolean outletBang(int outlet) { + doOutletBang(_outlets_ptr[outlet]); + return true; + } + + /** + * Sends a float to outlet x. + * @param outlet the outlet number to use + * @param value the float value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, float value) { + doOutletFloat(_outlets_ptr[outlet], value); + return true; + } + + /** + * Sends floats to outlet x. + * @param outlet the outlet number to use + * @param value the array of float to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, float value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a symbol to outlet x. + * @param outlet the outlet number to use + * @param value the symbol + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, String value) { + doOutletSymbol(_outlets_ptr[outlet], value); + return true; + } + + /** + * Sends symbols to outlet x. + * @param outlet the outlet number to use + * @param value the array of symbol to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, String value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a byte to outlet x. + * @param outlet the outlet number to use + * @param value the byte value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, byte value) { + return outlet(outlet, (float) value); + } + + /** + * Sends byte to outlet x. + * @param outlet the outlet number to use + * @param value the array of byte to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, byte value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a char to outlet x. + * @param outlet the outlet number to use + * @param value the char value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, char value) { + return outlet(outlet, (float) value); + } + + /** + * Sends char to outlet x. + * @param outlet the outlet number to use + * @param value the array of char to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, char value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a short to outlet x. + * @param outlet the outlet number to use + * @param value the short value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, short value) { + return outlet(outlet, (float) value); + } + + /** + * Sends shorts to outlet x. + * @param outlet the outlet number to use + * @param value the array of short to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, short value[]) { + doOutletAnything(_outlets_ptr[outlet], "list" , Atom.newAtom(value)); + return true; + } + + /** + * Sends a int to outlet x. + * @param outlet the outlet number to use + * @param value the int value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, int value) { + return outlet(outlet, (float) value); + } + + /** + * Sends ints to outlet x. + * @param outlet the outlet number to use + * @param value the array of int to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, int value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a long to outlet x. + * @param outlet the outlet number to use + * @param value the long value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, long value) { + return outlet(outlet, (float) value); + } + + /** + * Sends longs to outlet x. + * @param outlet the outlet number to use + * @param value the array of longs to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, long value[]) { + doOutletAnything(_outlets_ptr[outlet], "list", Atom.newAtom(value)); + return true; + } + + /** + * Sends a double to outlet x. + * @param outlet the outlet number to use + * @param value the double value + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, double value) { + return outlet(outlet, (float) value); + } + + /** + * Sends doubles to outlet x. + * @param outlet the outlet number to use + * @param value the array of double to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, double value[]) { + doOutletAnything(_outlets_ptr[outlet], "list" , Atom.newAtom(value)); + return true; + } + + /** + * Sends message with argument to outlet x. + * @param outlet the outlet number to use + * @param message the message symbol name + * @param value the arguments + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, String message, Atom[] value) { + doOutletAnything(_outlets_ptr[outlet], message, value); + return true; + } + + /** + * Sends atom value to outlet x. If it is a int/float, it will call + * <code>outlet(int, float)</code> otherwise <code>outlet(int, String)</code> + * @param outlet the outlet number to use + * @param value the atom value to send + * @return always true, since PD API returns void + */ + public final boolean outlet(int outlet, Atom value) { + if ( value.isFloat() || value.isInt() ) { + doOutletFloat(_outlets_ptr[outlet], value.toFloat()); + } else { + doOutletSymbol(_outlets_ptr[outlet], value.toString()); + } + return true; + } + + /** + * Sends atoms to outlet x. If the array contains only one item, + * <code>outlet(outlet, Atom value)</code> will be called. If the + * first element of the array is a float/int, list will be + * appended to the message. Otherwise, the first atom will be the + * message and the rest of it the arguments. + * @param outlet the outlet number to use + * @param value the arguments + * @return true or false if atom[] is empty + */ + public final boolean outlet(int outlet, Atom[] value) { + if ( value.length == 0 ) + return false; + + if ( value.length == 1 ) { + outlet(outlet, value[0]); + } else { + doOutletAnything(_outlets_ptr[outlet], null, value); + } + return true; + } + + // user methods + ///////////////////////////////////////////////////////////// + /** + * Called by PD when pdj receives a bang from an inlet. Use + * <code>getInlet()</code> to know which inlet has received the message. + */ + protected void bang() { + } + + /** + * This will be called if pd sends a float and the float method is not + * overridden. Use <code>getInlet()</code> to know which inlet has + * received the message. + * @param i int value + */ + protected void inlet(int i) { + } + + /** + * Called by PD when pdj receives a float from an inlet. Use + * <code>getInlet()</code> to know which inlet has received the message. + * @param f float value received from the inlet + */ + protected void inlet(float f) { + } + + /** + * Called by PD when pdj receives a list of atoms. Use + * <code>getInlet()</code> to know which inlet has received the message. + * @param args the list + */ + protected void list(Atom args[]) { + } + + /** + * Called by PD when pdj receives an un-overriden method. If you need + * to catch all messages, override this method. Use <code>getInlet()</code> + * to know which inlet has received the message. + * @param symbol first atom symbol representation + * @param args the arguments of the message + */ + protected void anything(String symbol, Atom[] args) { + post("pdj: object '" + name + "' doesn't understand " + symbol); + } + + // compatibility methods + ///////////////////////////////////////////////////////////// + public final boolean outletBangHigh(int outlet) { + return outletBang(outlet); + } + + public final boolean outletHigh(int outlet, int value) { + return outlet(outlet, value); + } + + public final boolean outletHigh(int outlet, float value) { + return outlet(outlet, value); + } + + public final boolean outletHigh(int outlet, double value) { + return outlet(outlet, value); + } + + public final boolean outletHigh(int outlet, String value) { + return outlet(outlet, value); + } + + public final boolean outletHigh(int outlet, String msg, Atom[] args) { + return outlet(outlet, msg, args); + } + + public final boolean outletHigh(int outlet, Atom[] value) { + return outlet(outlet, value); + } + + /** + * Returns the output stream of PDJ. + * @return the PrintStream output stream of PDJ + */ + public static com.cycling74.io.PostStream getPostStream() { + return new com.cycling74.io.PostStream(); + } + + /** + * Returns the error stream of PDJ. + * @return the PrintStream error stream of PDJ + */ + public static com.cycling74.io.ErrorStream getErrorStream() { + return new com.cycling74.io.ErrorStream(); + } + + // unimplementable methods + ///////////////////////////////////////////////////////////// + /** + * <b>NOT USED IN PD.</b> + */ + public void viewsource() { + } + + /** + * <b>NOT USED IN PD.</b> Throws <code>UnsupportdOperationException</code></b> + */ + public static Object getContext() { + throw new UnsupportedOperationException(); + } + + /** + * Returns the object representing the pd patch. + */ + public MaxPatcher getParentPatcher() { + return patch; + } + + /** + * <b>NOT USED IN PD.</b> + */ + protected void save() { + } + + /** + * <b>NOT USED IN PD.</b> + */ + protected void setInletAssist(String[] messages) { + } + + /** + * <b>NOT USED IN PD.</b> + */ + protected void setInletAssist(int index, String message) { + } + + /** + * <b>NOT USED IN PD.</b> + */ + protected void setOutletAssist(String[] messages) { + } + + /** + * <b>NOT USED IN PD.</b> + */ + protected void setOutletAssist(int index, String message) { + } + + /** + * <b>NOT USED IN PD.</b> + */ + protected void embedMessage(String msg, Atom[] args) { + } + + /** + * Useless, but in the original API. + */ + public void gc() { + System.gc(); + } + + /** + * Refresh/reinitialize the PDJ classloader. + */ + public void zap() { + PDJClassLoader.resetClassloader(); + } + + // not from original class + ///////////////////////////////////////////////////////////// + + /** + * Tries to get the attribute name[0] and send its value to + * the last outlet. + * @param name the name of the attribute + */ + void get(Atom[] name) throws IllegalAccessException, InvocationTargetException { + String attrName = name[0].toString(); + if ( attributes.containsKey(attrName) ) { + Attribute attr = (Attribute) attributes.get(attrName); + outlet(_outlets_ptr.length-1, attr.get()); + } else { + error(this.name + ": attribute not defined '" + attrName + "'"); + } + } + + /** + * Called by pdj to check if it is possible to set value x. + * @param the name of the setter + * @param arg [0] the value to set + * @return true if the setter has been set + */ + private boolean _trySetter(String name, Atom[] arg) { + if ( !attributes.containsKey(name) ) + return false; + Attribute attr = (Attribute) attributes.get(name); + attr.set(arg); + return true; + } + + /** + * Tries to instanciate a MaxObject. + * @param name fq java name + * @param _pdobj_ptr C pointer to pd object + * @param args objects arguments + */ + static synchronized MaxObject registerObject(String name, long _pdobj_ptr, Atom[] args_complete) { + try { + Class clz = PDJClassLoader.dynamicResolv(name); + MaxObject obj = null; + + // map arguments and attributes + List largs = new ArrayList(); + List lattr = new ArrayList(); + + for (int i=1;i<args_complete.length;i++) { + if ( args_complete[i].toString().startsWith("@") ) { + lattr.add(args_complete[i].toString().substring(1)); + if ( i+1>=args_complete.length ) { + post("pdj: " + name + ": attribute '" + args_complete[i].toString() + + "' must have a initial value"); + return null; + } + lattr.add(args_complete[++i]); + } else { + largs.add(args_complete[i]); + } + } + + Atom args[] = new Atom[largs.size()]; + for (int i=0;i<args.length;i++) { + args[i] = (Atom) largs.get(i); + } + + Class argType[] = new Class[1]; + argType[0] = Atom[].class; + + pushPdjPointer(_pdobj_ptr); + + // instanciate the object + if ( args.length > 0 ) { + try { + Object argValue[] = new Object[1]; + argValue[0] = args; + Constructor c = clz.getConstructor(argType); + obj = (MaxObject) c.newInstance(argValue); + } catch ( NoSuchMethodException e) { + popPdjPointer(); + post("pdj: object " + name + " has no constructor with Atom[] parameters"); + return null; + } + } else { + try { + Constructor c = clz.getConstructor(null); + obj = (MaxObject) c.newInstance(null); + } catch (NoSuchMethodException e) { + try { + Constructor c = clz.getConstructor(argType); + obj = (MaxObject) c.newInstance(new Object[0]); + } catch ( Exception e1 ) { + popPdjPointer(); + throw e1; + } + } + } + + // next we process attributes from the constructor arguments + Iterator i = lattr.iterator(); + while( i.hasNext() ) { + String attrName = (String) i.next(); + obj.declareAttribute(attrName); + Attribute attr = (Attribute) obj.attributes.get(attrName); + attr.set(new Atom[] { (Atom) i.next() }); + } + + obj.name = name; + obj.postInit(); + return obj; + } catch (PDJClassLoaderException e ) { + MaxSystem.post("pdj: " + e.toString()); + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + + /** + * Called after the constructor has been called. Check if the user + * has override the standard inlets/outlets. + */ + private void postInit() { + if ( _inlets_ptr == null ) { + declareInlets(new int[] { DataTypes.ANYTHING }); + } + + if ( _outlets_ptr == null ) { + declareOutlets(new int[] { DataTypes.ANYTHING }); + } + + // add the last/info outlet + if ( toCreateInfoOutlet ) { + long tmp[] = new long[_outlets_ptr.length+1]; + + System.arraycopy(_outlets_ptr, 0, tmp, 0, _outlets_ptr.length); + tmp[_outlets_ptr.length] = newOutlet(DataTypes.ANYTHING); + + _outlets_ptr = tmp; + } + } + + // native party + ///////////////////////////////////////////////////////////// + native private long newInlet(int type); + native private long newOutlet(int type); + + native private void doOutletBang(long ptr); + native private void doOutletFloat(long ptr, float value); + native private void doOutletSymbol(long ptr, String symbol); + native private void doOutletAnything(long ptr, String msg, Atom []args); + + native private String getPatchPath(); + + // ugly ugly... but MaxContext vs this; I prefer this.... + native private static void pushPdjPointer(long ptr); + native private static long popPdjPointer(); +} diff --git a/src/java/com/cycling74/max/MaxPatcher.java b/src/java/com/cycling74/max/MaxPatcher.java new file mode 100644 index 0000000..b2fb778 --- /dev/null +++ b/src/java/com/cycling74/max/MaxPatcher.java @@ -0,0 +1,17 @@ +package com.cycling74.max; + +/** + * Java object that encapsulate the pd patch. Minimal support on pdj; only + * getPath() is supported. + */ +public class MaxPatcher { + String patchPath; + + /** + * Returns the absolute path of the associated patch. + * @return the absolute patch path + */ + public String getPath() { + return patchPath; + } +} diff --git a/src/java/com/cycling74/max/MaxQelem.java b/src/java/com/cycling74/max/MaxQelem.java new file mode 100644 index 0000000..100f671 --- /dev/null +++ b/src/java/com/cycling74/max/MaxQelem.java @@ -0,0 +1,114 @@ +package com.cycling74.max; + +/** + * Background job utility class. This is used to execute code in the + * background that might take time to execute. Calling <code>set</code> + * will trigger/execute the "job". If <code>set</code> is called while + * the "job" is running, the "job" won't be called again. + * <p>On PDJ, a Qelem is simply a Java thread that wait to be triggered. + * </p> + */ +public class MaxQelem { + Thread job; + Executable exec; + boolean incall; + static int nbQelem = 0; + boolean stopThread = true; + + /** + * Constructs a Qelem that is bound the overriden class with method + * name qfn. + */ + public MaxQelem() { + exec = new Callback(this, "qfn"); + do_init(); + } + + /** + * Constructs a Qelem that is bound to an executable. + * @param exec the executable to run + */ + public MaxQelem(Executable exec) { + this.exec = exec; + do_init(); + } + + /** + * Constructs a Qelem that is bound to a class with method name. + * @param src the object that contains the method + * @param method the method to execute + */ + public MaxQelem(Object src, String method) { + exec = new Callback(src, method); + do_init(); + } + + private void do_init() { + job = new Thread(new Dispatcher(), "MaxQelem#" + (++nbQelem)); + job.setPriority(Thread.MIN_PRIORITY); + } + + /** + * Puts thread in front execution. <b>Does nothing on PDJ</b> + */ + public void front() { + } + + /** + * The callback method, otherwise it calls the 'executable' method + * provided in constructor. + */ + public void qfn() { + } + + /** + * Ask qfn to execute. If it is already set, the qfn won't be called + * twice. + */ + public synchronized void set() { + if ( !incall ) { + job.notify(); + } + } + + /** + * Cancels execution of qelem. Use with caution since it will throw + * an InterruptedException on PDJ. + */ + public synchronized void unset() { + job.interrupt(); + } + + + /** + * Releases current Qelem and cancel the running thread. + */ + public void release() { + unset(); + stopThread = true; + } + + /** + * Returns the executable object. + * @return the executble object that is bound to this Qelem + */ + public Executable getExecutable() { + return exec; + } + + class Dispatcher implements Runnable { + public void run() { + while( !stopThread ) { + try { + incall = false; + job.wait(); + incall = true; + exec.execute(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } +} diff --git a/src/java/com/cycling74/max/MaxRuntimeException.java b/src/java/com/cycling74/max/MaxRuntimeException.java new file mode 100644 index 0000000..9909a5c --- /dev/null +++ b/src/java/com/cycling74/max/MaxRuntimeException.java @@ -0,0 +1,15 @@ +package com.cycling74.max; + +/** + * API based runtime exception with Max/PDJ. + */ +public class MaxRuntimeException extends RuntimeException { + + public MaxRuntimeException(String msg) { + super(msg); + } + + public MaxRuntimeException() { + } + +} diff --git a/src/java/com/cycling74/max/MaxSystem.java b/src/java/com/cycling74/max/MaxSystem.java new file mode 100644 index 0000000..3a29540 --- /dev/null +++ b/src/java/com/cycling74/max/MaxSystem.java @@ -0,0 +1,186 @@ +package com.cycling74.max; + +import com.e1.pdj.*; + +/** + * MXJ System utilities. + */ +public class MaxSystem { + static private PriorityQueue low = new PriorityQueue(Thread.MIN_PRIORITY); + + /** + * Shows a message to the pd console + * @param message the string to show + */ + static native public void post(String message); + + /** + * Shows a error message to the pd console + * @param message the string to show + */ + static native public void error(String message); + + /** + * Shows a message in the pd console and kill PD afterwards. + * @param message the string to show + */ + static native public void ouch(String message); + + /** + * Sends a message to a bound object (IEM object or a receiver) + * @param name the destination of the message + * @param msg the symbol message ("bang", "float", "list") + * @param args the array of Atoms + * @return true if successfull + */ + static native public boolean sendMessageToBoundObject(String name, String msg, Atom[] args); + + /** + * Tries to locate file in pure-data search path. + * @param filename of the file to search in path + * @return the full path of this file + */ + static native public String locateFile(String filename); + + /** + * Will schedule the executable to a low priority thread + * @param fn the executable + */ + static synchronized public void deferLow(Executable fn) { + low.defer(fn); + } + + /** + * Will schedule the executable to a medium priority thread + * @param fn the executable + */ + static synchronized public void deferMedium(Executable fn) { + low.defer(fn); + } + + /** + * Returns the user classpath. + * @return Array of strings of each entries in the user classpath + */ + static public String[] getClassPath() { + return PDJClassLoader.getCurrentClassPath(); + } + + // implemented but not fully supported... + static public void defer(Executable fn) { + fn.execute(); + } + static public void deferFront(Executable fn) { + fn.execute(); + } + + static public String[] getSystemClassPath() { + return null; + } + + static boolean inMainThread() { + return false; + } + + static boolean inMaxThread() { + return false; + } + + static boolean inTimerThread() { + return false; + } + + static boolean isOsMacOsX() { + String osname = System.getProperty("os.name"); + if ( osname.indexOf("OS X") != -1 ) { + return true; + } + return false; + } + + static boolean isOsWindows() { + String osname = System.getProperty("os.name"); + if ( osname.indexOf("Windows") != -1 ) { + return true; + } + return false; + } + + /** + * <b>Not supported in PD</b> + */ + static void registerCommandAccelerator(char c) { + } + + /** + * <b>Not supported in PD</b> + */ + static void registerCommandAccelerators(char c[]) { + } + + /** + * <b>Not supported in PD</b> + */ + static void unRegisterCommandAccelerator(char c) { + } + /** + * <b>Not supported in PD</b> + */ + static void unRegisterCommandAccelerators(char c[]) { + } + + // not compatible Max methods : + //////////////////////////////////////////////////////// + /** + * <b>Not supported in PD</b> + */ + static public boolean isStandAlone() { + return false; + } + + static public short getMaxVersion() { + return 0; + } + + static public int[] getMaxVersionInts() { + int ret[] = new int[3]; + + ret[0] = 0; + ret[1] = 99; + ret[2] = 0; + + return ret; + } + + /** + * <b>Not supported in PD</b> + */ + static public void hideCursor() { + } + + /** + * <b>Not supported in PD</b> + */ + static public void showCursor() { + } + + /** + * <b>Not supported in PD</b> + */ + static public void nextWindowIsModal() { + } + + // constants + public static String MXJ_VERSION = "pdj 0.8.3"; + + public static final int PATH_STYLE_COLON = 2; + public static final int PATH_STYLE_MAX = 0; + public static final int PATH_STYLE_NATIVE = 1; + public static final int PATH_STYLE_NATIVE_WIN = 4; + public static final int PATH_STYLE_SLASH = 3; + public static final int PATH_TYPE_ABSOLUTE = 1; + public static final int PATH_TYPE_BOOT = 3; + public static final int PATH_TYPE_C74 = 4; + public static final int PATH_TYPE_IGNORE = 0; + public static final int PATH_TYPE_RELATIVE = 2; +} diff --git a/src/java/com/cycling74/max/MessageReceiver.java b/src/java/com/cycling74/max/MessageReceiver.java new file mode 100644 index 0000000..3310de0 --- /dev/null +++ b/src/java/com/cycling74/max/MessageReceiver.java @@ -0,0 +1,11 @@ +package com.cycling74.max; + +/** + * This is a utility callback class that can be used to notify background job. + */ +public interface MessageReceiver { + /** + * Called when a event has occured. + */ + public void messageReceived(Object src, int messageId, Object data); +} diff --git a/src/java/com/cycling74/max/package.html b/src/java/com/cycling74/max/package.html new file mode 100644 index 0000000..dcd23c3 --- /dev/null +++ b/src/java/com/cycling74/max/package.html @@ -0,0 +1,5 @@ +<html> +<body> +<p>Basic package for PDJ</p> +</body> +</html>
\ No newline at end of file diff --git a/src/java/com/cycling74/msp/AudioFileBuffer.java b/src/java/com/cycling74/msp/AudioFileBuffer.java new file mode 100644 index 0000000..b2e76f3 --- /dev/null +++ b/src/java/com/cycling74/msp/AudioFileBuffer.java @@ -0,0 +1,116 @@ +package com.cycling74.msp; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + +import com.cycling74.max.MessageReceiver; + +/** + * Work in progress, target: 0.8.4 + */ +class AudioFileBuffer { + + /** file buffer value */ + public float buf[][]; + + /** Value of the message that will be sended to MessageReceiver when the file will be loaded */ + public static final int FINISHED_READING = 1; + + private MessageReceiver callback = null; + private String file; + private AudioFormat audioFormat; + private AudioFormat targetAudioFormat; + + // getters values + private float sampleRate; + private int sampleBitFormat; + private boolean sampleBigEndian; + private int sampleChannels; + private long sampleFrames; + + public AudioFileBuffer(String filename) throws FileNotFoundException, IOException, UnsupportedAudioFileException { + open(filename); + } + + public AudioFileBuffer(String filename, MessageReceiver callback) throws FileNotFoundException, IOException, UnsupportedAudioFileException { + this.callback = callback; + open(filename); + } + + public void open(String filename) throws FileNotFoundException, IOException, UnsupportedAudioFileException { + // file format + sampleRate = 0; + sampleBitFormat = 0; + sampleChannels = 0; + sampleFrames = 0; + + AudioInputStream sourceAudioInputStream = AudioSystem.getAudioInputStream( new FileInputStream(file) ); + AudioFormat sourceAudioFormat = sourceAudioInputStream.getFormat(); + targetAudioFormat = new AudioFormat( + AudioFormat.Encoding.PCM_UNSIGNED, // Encoding + getSystemSampleRate(), // Sample rate + 16, // bit + sourceAudioFormat.getChannels(), // channel + sourceAudioFormat.getFrameSize(), // frame size + sourceAudioFormat.getFrameRate(), // frame rate + false ); // big Endian + AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(audioFormat, sourceAudioInputStream ); + + FileLoader thread = new FileLoader(audioInputStream); + new Thread(thread).start(); + } + + class FileLoader implements Runnable { + AudioInputStream ais; + + FileLoader(AudioInputStream input) { + ais = input; + } + + public void run() { + try { + buf = new float[audioFormat.getChannels()][]; + + for (int i=0;ais.available() != 0;i++) { + //doc.content[i] = audioInputStream.read() / 65535; + } + + if ( callback != null ) + callback.messageReceived(this, FINISHED_READING, null); + } catch ( IOException e ) { + } + } + } + + public float getSampleRate() { + return sampleRate; + } + + public int getSampleSizeInBits() { + return sampleBitFormat; + } + + public boolean isBigEndian() { + return sampleBigEndian; + } + + public long getFrameLength() { + return sampleFrames; + } + + public int getChannels() { + return sampleChannels; + } + + public float getLengthMs() { + return buf[0].length / (sampleRate / 1000); + } + + private native int getSystemSampleRate(); +}
\ No newline at end of file diff --git a/src/java/com/cycling74/msp/MSPBuffer.java b/src/java/com/cycling74/msp/MSPBuffer.java new file mode 100644 index 0000000..70b730a --- /dev/null +++ b/src/java/com/cycling74/msp/MSPBuffer.java @@ -0,0 +1,127 @@ +package com.cycling74.msp; + +/** + * Used to get or set pd array content. Please note that the channel parameter + * is added in the API to match Max/MSP MSPBuffer signature. + */ +public class MSPBuffer { + /** + * Returns the array content. + * @param name the array name + * @return the array contents + */ + public static float[] peek(String name) { + return getArray(name, 0, -1); + } + + /** + * Returns the array content. + * @param name the array name + * @param channel <i>not used in pd</i> + * @return the array contents + */ + public static float[] peek(String name, int channel) { + return getArray(name, 0, -1); + } + + /** + * Returns the array content. + * @param name the array name + * @param channel <i>not used in pd</i> + * @param start the start index of the array + * @param length the size of the array to return + * @return the array contents + */ + public static float[] peek(String name, int channel, long start, long length) { + return getArray(name, start, length); + } + + /** + * Returns the array content value at a specific position. + * @param name the array name + * @param channel <i>not used in pd</i> + * @param index the start index of the array + * @return the value stored at index <code>start</code> + */ + public static float peek(String name, int channel, long index) { + float ret[] = getArray(name, index, 1); + if ( ret == null ) + return 0; + return ret[0]; + } + + /** + * Sets array content. + * @param name the array name + * @param values the array to set + */ + public static void poke(String name, float values[]) { + setArray(name, 0, values); + } + + /** + * Sets array content. + * @param name the array name + * @param channel <i>not used in pd</i> + * @param values the array to set + */ + public static void poke(String name, int channel, float values[]) { + setArray(name, 0, values); + } + + /** + * Sets array content. + * @param name the array name + * @param channel <i>not used in pd</i> + * @param start the start index of the array + * @param values the array to set + */ + public static void poke(String name, int channel, long start, float values[]) { + setArray(name, start, values); + } + + /** + * Set a value in a array. + * @param name the array name + * @param channel <i>not used in pd</i> + * @param index the index in the array to set + * @param value the value to set in the array + */ + public static void poke(String name, int channel, long index, float value) { + float content[] = new float[1]; + content[0] = value; + setArray(name, index, content); + } + + /** + * Sets the array size. + * @param name the array name + * @param numchannel <i>not used in pd</i> + * @param size the new array size; + */ + public static native void setSize(String name, int numchannel, long size); + + /** + * Returns the array size + * @param name the array name + * @return the array size or -1 if not found + */ + public static native long getSize(String name); + + private static native float[] getArray(String name, long from, long size); + private static native void setArray(String name, long from, float[]content); + private MSPBuffer() {} + + /** + * Return the number of channel for this array. Useless in PD cause there + * is no channels in a array. + * @param name array name. + * @return always returns 1 on pd; unless the name is not defined. + */ + public static int getChannel(String name) { + // resolv the name + if ( getSize(name) != -1 ) + return 1; + return -1; + } +} diff --git a/src/java/com/cycling74/msp/MSPObject.java b/src/java/com/cycling74/msp/MSPObject.java new file mode 100644 index 0000000..be2d68b --- /dev/null +++ b/src/java/com/cycling74/msp/MSPObject.java @@ -0,0 +1,163 @@ +package com.cycling74.msp; + +import java.lang.reflect.Method; + +import com.cycling74.max.DataTypes; +import com.cycling74.max.MaxObject; + +/** + * Main object to extend to use dsp with pd. You will need to + * use the pdj~ external if you want to process signals. Before + * the dsp starts processing, the method dsp will be called and it must + * return the performer method that will process each dsp cycles. + * + * <p>Here is a basic guideline for using the MSPObject:</p> + * <p><blockquote><pre> + * import com.cycling74.max.*; + * import com.cycling74.msp.*; + * import java.lang.reflection.Method; + * + * public class panner extends MSPObject { + * float left = 1, right = 1; + * + * public panner() { + * declareInlets( new int[] { SIGNAL, DataTypes.ANYTHING } ); + * declareOutlets( new int[] { SIGNAL, SIGNAL } ); + * } + * + * // From 0..127 + * public void inlet(float val) { + * if ( val > 64 ) { + * right = 1; + * left = ((127-val) / 64); + * } else { + * left = 1; + * right = val / 64; + * } + * } + * + * public Method dsp(MSPSignal[] ins, MSPSignal[] outs) { + * return getPerformMethod("perform"); + * } + * + * public void perform(MSPSignal[] ins, MSPSignal[] outs) { + * for (int i=0;i<ins[0].n;i++) { + * outs[0].vec[i] = ins[0].vec[i] * left; + * outs[1].vec[i] = ins[0].vec[i] * right; + * } + * } + * } + * </p></blockquote></pre> + */ +public abstract class MSPObject extends MaxObject { + /** + * Use this value to indentify a signal intlet/outlet to + * the method declareInlet/declareOutlet. + */ + public final static int SIGNAL = 32; + + /** + * Initialize the dsp state. From the numbers of input/output this + * method must return a performing method. + * @param ins input signals + * @param outs output signals + * @return the method to execute at each dsp cycle + */ + public abstract Method dsp(MSPSignal[] ins, MSPSignal[] outs); + + /** + * Quicky method to be used with dsp(MSPSignal[], MSPSignal[]). It + * will return the method name <code>methodName</code> with signature + * (MSPSignal[], MSPSignal[]) in the current class. + * @param methodName the name of the method in the current class + * @return the method reflection + */ + protected Method getPerformMethod(String methodName) { + try { + Method m = getClass().getDeclaredMethod(methodName, new Class[] { + MSP_SIGNAL_ARRAY_CLZ, MSP_SIGNAL_ARRAY_CLZ }); + return m; + } catch ( NoSuchMethodException e ) { + error("pdj~: method: " + methodName + " not found in class in:" + getClass().toString()); + } + return null; + } + + /** + * Force to copy of each MSPBuffer when performer is called. Right now, + * on pdj the MSPBuffer is always copied. + * @param copyBuffer true if you need to copyBuffer + */ + protected void setNoInPlace(boolean copyBuffer) { + //this.copyBuffer = copyBuffer; + } + + /** + * This method is called when the dsp is start/stop. + * <b>NOT USED ON PD.</b> + * @param dspRunning true if the dsp is running + */ + protected void dspstate(boolean dspRunning) { + } + + + /** + * Declare the inlets used by this object. Use MSPObject.SIGNAL + * to define a signal inlet. <b>Note:</b> any signal inlet won't + * be able to process any atom messages. + */ + protected void declareInlets(int[] types) { + int i, inlets = 0; + for (i=0;i<types.length;i++) { + if ( types[i] == SIGNAL ) + inlets++; + } + _used_inputs = new MSPSignal[inlets]; + for(i=0;i<_used_inputs.length;i++) { + _used_inputs[i] = new MSPSignal(); + } + super.declareInlets(types); + } + + /** + * Declare the outlets used by this object. Use MSPObject.SIGNAL + * to define a signal outlet. + */ + protected void declareOutlets(int[] types) { + int i, outlets = 0; + for (i=0;i<types.length;i++) { + if ( types[i] == SIGNAL ) + outlets++; + } + _used_outputs = new MSPSignal[outlets]; + for(i=0;i<_used_outputs.length;i++) { + _used_outputs[i] = new MSPSignal(); + } + super.declareOutlets(types); + } + + public static final Class MSP_SIGNAL_ARRAY_CLZ = (new MSPSignal[0]).getClass(); + + private MSPSignal[] _used_inputs = new MSPSignal[0]; + private MSPSignal[] _used_outputs = new MSPSignal[0]; + + private Method _dspinit(float sr, int vector_size) { + int i; + for(i=0;i<_used_inputs.length;i++) { + _used_inputs[i].n = vector_size; + _used_inputs[i].vec = new float[vector_size]; + _used_inputs[i].sr = sr; + } + for(i=0;i<_used_outputs.length;i++) { + _used_outputs[i].n = vector_size; + _used_outputs[i].vec = new float[vector_size]; + _used_outputs[i].sr = sr; + } + dspstate(true); + return dsp(_used_inputs, _used_outputs); + } + + private void _emptyPerformer(MSPSignal[] ins, MSPSignal[] outs) { + } +} + diff --git a/src/java/com/cycling74/msp/MSPPerformable.java b/src/java/com/cycling74/msp/MSPPerformable.java new file mode 100644 index 0000000..6f27241 --- /dev/null +++ b/src/java/com/cycling74/msp/MSPPerformable.java @@ -0,0 +1,17 @@ +package com.cycling74.msp; + +/** + * This interface is used to chain Java dsp objects since they don't + * have to be MSPObject to process signals. + */ +public interface MSPPerformable { + /** + * @see MSPPerformer.dspsetup(MSPSignal in[], MSPSignal out[]) + */ + public void dspsetup(MSPSignal in[], MSPSignal out[]); + + /** + * @see MSPPerformer.perform(MSPSignal in[], MSPSignal out[]); + */ + public void perform(MSPSignal in[], MSPSignal out[]); +} diff --git a/src/java/com/cycling74/msp/MSPPerformer.java b/src/java/com/cycling74/msp/MSPPerformer.java new file mode 100644 index 0000000..2201d82 --- /dev/null +++ b/src/java/com/cycling74/msp/MSPPerformer.java @@ -0,0 +1,36 @@ +package com.cycling74.msp; + +import java.lang.reflect.Method; + +/** + * Process signals with a single method. Like MSPObject except that + * the performer method will always be "perform" (and you override it). + */ +public abstract class MSPPerformer extends MSPObject implements MSPPerformable { + + /** + * Called by PD with the dsp message is sended. This method will then + * call dspsetup and map the method "perform" has the performer method. + * You don't have to override this method, use dspsetup if initialization + * needs to be done before the signals starts processing. + */ + public Method dsp(MSPSignal[] in, MSPSignal[] out) { + dspsetup(in, out); + return getPerformMethod("perform"); + } + + /** + * Initialize the dsp state of the MSPPerformable. Override this if + * you have to initalize something before the signal starts + * processing. + */ + public void dspsetup(MSPSignal[] in, MSPSignal[] out) { + } + + /** + * Process signal inlets/outlets. You must override this since it is + * the main processing method. + */ + public abstract void perform(MSPSignal[] in, MSPSignal[] out); + +} diff --git a/src/java/com/cycling74/msp/MSPSignal.java b/src/java/com/cycling74/msp/MSPSignal.java new file mode 100644 index 0000000..0fb9635 --- /dev/null +++ b/src/java/com/cycling74/msp/MSPSignal.java @@ -0,0 +1,70 @@ +package com.cycling74.msp; + +/** + * Signals representation; signals inlets will copy signal data to this + * object. + */ +public class MSPSignal { + + /** + * Tells the number of time this signal is connected. This is always + * 1 in pure-data. + */ + public short cc = 1; + + /** + * Tells if this signal is connected. This is always true in pure-data. + */ + public boolean connected = true; + + /** + * Number of sample in vector. + */ + public int n; + + /** + * The sampling rate. + */ + public double sr; + + /** + * The current sample data. + */ + public float[] vec; + + MSPSignal() { + } + + public MSPSignal(float vec[], double sr, int n, short cc) { + this.vec = vec; + this.sr = sr; + this.n = n; + this.cc = cc; + } + + /** + * Returns a copy of this signal but with the same sample buffer. + * @return the new signal with the same buffer + */ + public MSPSignal alias() { + return new MSPSignal(vec, sr, n, cc); + } + + /** + * Returns the a duplicated signal object. Also copies the array. + * @return the new signal with the same value + */ + public MSPSignal dup() { + return new MSPSignal((float [])vec.clone(), sr, n, cc); + } + + /** + * Returns the a duplicated signal object, but the sample vector is + * re-initialized. + * @return the new signal with empty value (silence) + */ + public MSPSignal dupclean() { + return new MSPSignal(new float [vec.length], sr, n, cc); + } + +} diff --git a/src/java/com/cycling74/msp/package.html b/src/java/com/cycling74/msp/package.html new file mode 100644 index 0000000..17f4051 --- /dev/null +++ b/src/java/com/cycling74/msp/package.html @@ -0,0 +1,5 @@ +<html> +<body> +<p>Package for using using array and signals objects.</p> +</body> +</html>
\ No newline at end of file diff --git a/src/java/com/e1/pdj/ConsoleStream.java b/src/java/com/e1/pdj/ConsoleStream.java new file mode 100644 index 0000000..cf396c7 --- /dev/null +++ b/src/java/com/e1/pdj/ConsoleStream.java @@ -0,0 +1,35 @@ +package com.e1.pdj; + +import java.io.*; + +import com.cycling74.max.MaxSystem; + +public class ConsoleStream extends OutputStream { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(4096); + + protected void send(String message) { + MaxSystem.post("pdj: " + message); + } + + public void flush() { + String msg = buffer.toString(); + if ( msg.endsWith("\n") ) { + msg = msg.substring(0, msg.length()-1); + } + if ( !msg.equals("") ) + send(buffer.toString()); + buffer.reset(); + } + + public void write(byte[] b) throws IOException { + buffer.write(b); + } + + public void write(int b) throws IOException { + buffer.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + buffer.write(b, off, len); + } +} diff --git a/src/java/com/e1/pdj/ConsoleStreamWin32.java b/src/java/com/e1/pdj/ConsoleStreamWin32.java new file mode 100644 index 0000000..80f767e --- /dev/null +++ b/src/java/com/e1/pdj/ConsoleStreamWin32.java @@ -0,0 +1,34 @@ +package com.e1.pdj; + +import com.cycling74.max.MaxSystem; + +/** + * Win32 has a special stream since it can contains /r/n that will + * be duplicated in the console + */ +public class ConsoleStreamWin32 extends ConsoleStream { + protected void send(String message) { + StringBuffer ret = new StringBuffer(); + + for (int i=0;i<message.length();i++) { + char c = message.charAt(i); + + if ( c != '\r' ) + ret.append(c); + } + + message = ret.toString(); + if ( !message.equals("\n") ) + MaxSystem.post("pdj: " + ret.toString()); + } + + public void flush() { + String msg = buffer.toString(); + if ( msg.endsWith("\n") ) { + msg = msg.substring(0, msg.length()-1); + } + if ( !msg.equals("") ) + send(buffer.toString()); + buffer.reset(); + } +} diff --git a/src/java/com/e1/pdj/GenericCompiler.java b/src/java/com/e1/pdj/GenericCompiler.java new file mode 100644 index 0000000..31278b7 --- /dev/null +++ b/src/java/com/e1/pdj/GenericCompiler.java @@ -0,0 +1,71 @@ +package com.e1.pdj; + +import java.io.*; + +import com.cycling74.max.MaxSystem; + +abstract class GenericCompiler { + File javaFile; + String cp[]; + String compilerName; + + static String rtJar; + + GenericCompiler(String name) { + compilerName = name + ": "; + } + + String getConfigurationClassPath() { + return PDJClassLoader.getConfigurationClassPath(); + } + + File resolvJavaFile() { + int base = PDJClassLoader.fclasses.toString().length() + 1; + String className = javaFile.toString().substring(base); + return new File(className); + } + + private int doexec(String arg) throws Exception { + Process p = Runtime.getRuntime().exec(arg, null, PDJClassLoader.fclasses); + + BufferedReader stdout = new BufferedReader(new InputStreamReader(p.getInputStream())); + BufferedReader stderr = new BufferedReader(new InputStreamReader(p.getErrorStream())); + String buff; + boolean cont = true; + while(cont) { + cont = false; + + buff = stdout.readLine(); + if ( buff != null ) { + MaxSystem.post(compilerName + buff); + cont = true; + } else { + cont = false; + } + + buff = stderr.readLine(); + if ( buff != null ) { + MaxSystem.post(compilerName + buff); + cont = true; + } else { + cont = cont & false; + } + } + + p.waitFor(); + + return p.exitValue(); + } + + int exec(String arg) throws PDJClassLoaderException { + MaxSystem.post("pdj: trying to compile class: " + resolvJavaFile().toString()); + + try { + return doexec(arg); + } catch (Exception e) { + throw new PDJClassLoaderException(e); + } + } + + abstract void compileClass() throws PDJClassLoaderException; +} diff --git a/src/java/com/e1/pdj/JavacCompiler.java b/src/java/com/e1/pdj/JavacCompiler.java new file mode 100644 index 0000000..7f2debe --- /dev/null +++ b/src/java/com/e1/pdj/JavacCompiler.java @@ -0,0 +1,34 @@ +package com.e1.pdj; + +import com.cycling74.max.MaxSystem; +import java.io.File; + +class JavacCompiler extends GenericCompiler { + + public JavacCompiler() { + super("javac"); + } + + String javacPath() { + String fullPath = System.getProperty("pdj.JAVA_HOME"); + File test = new File(new File(fullPath, "bin"), "javac"); + if ( test.exists() ) { + return test.getAbsolutePath(); + } + MaxSystem.post("unable to find 'bin/javac' from the JAVA_HOME, using PATH"); + return "javac"; + } + + void compileClass() throws PDJClassLoaderException { + String args = javacPath() + " " + resolvJavaFile() + + " -classpath " + getConfigurationClassPath() + + " -sourcepath " + PDJClassLoader.fclasses.toString(); + + int rc = exec(args); + + if ( rc != 0 ) { + throw new PDJClassLoaderException("pdj: compiler returned: "+ rc + ",args: " +args); + } + MaxSystem.post("pdj: compile successful"); + } +} diff --git a/src/java/com/e1/pdj/JikesCompiler.java b/src/java/com/e1/pdj/JikesCompiler.java new file mode 100644 index 0000000..f9337b2 --- /dev/null +++ b/src/java/com/e1/pdj/JikesCompiler.java @@ -0,0 +1,27 @@ +package com.e1.pdj; + +import java.io.*; +import com.cycling74.max.MaxSystem; + +public class JikesCompiler extends GenericCompiler { + + JikesCompiler() { + super("jikes"); + } + + void compileClass() throws PDJClassLoaderException { + if ( GenericCompiler.rtJar == null ) + throw new PDJClassLoaderException("pdj: JAVA_HOME not found"); + + String args = "jikes " + resolvJavaFile() + + " -classpath " + GenericCompiler.rtJar + getConfigurationClassPath() + + " -sourcepath " + PDJClassLoader.fclasses.toString(); + + int rc = exec(args); + + if ( rc != 0 ) { + throw new PDJClassLoaderException("pdj: compiler returned: "+ rc + ",args: " +args); + } + MaxSystem.post("pdj: compile successful"); + } +} diff --git a/src/java/com/e1/pdj/PDJClassLoader.java b/src/java/com/e1/pdj/PDJClassLoader.java new file mode 100644 index 0000000..ee54e71 --- /dev/null +++ b/src/java/com/e1/pdj/PDJClassLoader.java @@ -0,0 +1,206 @@ +package com.e1.pdj; + +import java.net.URLClassLoader; +import java.net.*; +import java.util.*; +import java.io.*; + +import com.cycling74.max.MaxSystem; + +public class PDJClassLoader extends URLClassLoader { + /** The folder where the class (and java files) are */ + static File fclasses; + + /** if this is set to true, all classloader operation will be logged */ + static boolean verboseCL = false; + + static private PDJClassLoader instance; + + static { + String prop = System.getProperty("pdj.classes-dir"); + + if ( prop == null ) { + fclasses = new File(System.getProperty("pdj.home") + "/classes"); + } else { + fclasses = new File(prop); + } + instance = new PDJClassLoader(); + + verboseCL = PDJSystem.isSystemPropertyTrue("pdj.verbose-classloader"); + } + + static public PDJClassLoader getInstance() { + return instance; + } + + public PDJClassLoader() { + super(resolvClasspath()); + } + + private static void findJars(File f, Collection v) throws MalformedURLException { + File files[] = f.listFiles(); + + for(int i = 0;i<files.length;i++) { + String filename = files[i].toString(); + if ( filename.endsWith(".jar") || filename.endsWith(".zip") ) { + v.add(new URL("jar:" + files[i].toURL().toString() + "!/")); + } + } + } + + private static URL[] resolvClasspath() { + String cp = System.getProperty("pdj.classpath"); + ArrayList list = new ArrayList(); + String pathSeparator = "" + File.pathSeparator; + + try { + if ( fclasses.isDirectory() ) + list.add(fclasses.toURL()); + + File lib = new File(System.getProperty("pdj.home") + "/lib"); + if ( lib.isDirectory() ) + findJars(lib, list); + + StringTokenizer st = new StringTokenizer(cp, pathSeparator); + while( st.hasMoreTokens() ) { + String token = st.nextToken(pathSeparator); + File f = new File(token); + if ( f.isFile() && (f.toString().endsWith(".jar") || f.toString().endsWith(".zip")) ) + list.add(new URL("jar:" + f.toURL().toString() + "!/")); + if ( f.isDirectory() ) { + findJars(f, list); + list.add(f.toURL()); + } + } + } catch ( MalformedURLException mue ) { + throw new Error("pdj: unable to add to classpath: " + mue); + } + + URL ret[] = (URL[]) list.toArray(new URL[list.size()]); + + if ( verboseCL ) { + MaxSystem.post("pdj: verbose classloader: system classpath: " + System.getProperty("java.class.path")); + MaxSystem.post("pdj: verbose classloader: dynamic classpath:"); + for(int i=0;i<ret.length;i++) { + MaxSystem.post("\t" + ret[i].toString()); + } + } + + return ret; + } + + public static String[] getCurrentClassPath() { + URL url[] = instance.getURLs(); + String ret[] = new String[url.length]; + + for(int i=0;i<url.length;i++) { + ret[i] = url[i].toString(); + } + return ret; + } + + public static String getReadableClassPath() { + PDJClassLoader cloader = getInstance(); + StringBuffer sb = new StringBuffer(); + + URL url[] = cloader.getURLs(); + for(int i=0;i<url.length;i++) { + sb.append("\t" + url[i].toString() + "\n"); + } + + return sb.toString(); + } + + /** + * Returns a readable classpath for javac + * @return the classpath for javac / jikes + */ + public static String getConfigurationClassPath() { + StringBuffer sb = new StringBuffer(); + + sb.append(System.getProperty("java.class.path") + File.pathSeparatorChar); + + URL url[] = getInstance().getURLs(); + for(int i=0;i<url.length;i++) { + String path = url[i].toString(); + + if ( path.startsWith("file:") ) { + sb.append(path.substring(6) + File.pathSeparatorChar); + continue; + } + + if ( path.startsWith("jar:") ) { + sb.append(path.substring(10, path.lastIndexOf("!")) + File.pathSeparatorChar); + continue; + } + + sb.append(path + File.pathSeparatorChar); + } + + return sb.toString(); + } + + public static void resetClassloader() { + instance = new PDJClassLoader(); + System.gc(); + } + + /** + * Dynamic resolv will try to load .java file from date with .class + * @param name classname + * @return class definition + * @throws ClassNotFoundException + */ + public static Class dynamicResolv(String name) throws PDJClassLoaderException { + if ( System.getProperty("pdj.compiler").equals("null") ) { + resetClassloader(); + try { + return instance.loadClass(name); + } catch (ClassNotFoundException e) { + throw new PDJClassLoaderException(e); + } + } + + String pathName = name.replace('.', File.pathSeparatorChar); + + File classFile = new File(fclasses, pathName + ".class"); + File javaFile = new File(fclasses, pathName + ".java"); + + if ( javaFile.exists() ) { + if ( classFile.exists() ) { + if ( javaFile.lastModified() > classFile.lastModified() ) { + compileClass(javaFile); + } else { + if ( verboseCL ) + MaxSystem.post("class: " + name + " is already compiled and younger than the source .java file"); + } + } else { + compileClass(javaFile); + } + } + + resetClassloader(); + + try { + return instance.loadClass(name); + } catch (ClassNotFoundException e) { + throw new PDJClassLoaderException(e); + } + } + + protected static void compileClass(File javaFile) throws PDJClassLoaderException { + String str_compiler = System.getProperty("pdj.compiler"); + GenericCompiler compiler = null; + + if ( str_compiler.equals("jikes") ) { + compiler = new JikesCompiler(); + } else { + if ( !str_compiler.equals("javac") ) { + System.err.println("pdj: unknown compiler:" + str_compiler + ", using javac."); + } + compiler = new JavacCompiler(); + } + compiler.javaFile = javaFile; + compiler.compileClass(); + } +} diff --git a/src/java/com/e1/pdj/PDJClassLoaderException.java b/src/java/com/e1/pdj/PDJClassLoaderException.java new file mode 100644 index 0000000..547853c --- /dev/null +++ b/src/java/com/e1/pdj/PDJClassLoaderException.java @@ -0,0 +1,17 @@ +package com.e1.pdj; + +public class PDJClassLoaderException extends Exception { + private static final long serialVersionUID = 8714491904913363787L; + + public PDJClassLoaderException(String msg) { + super(msg); + } + + public PDJClassLoaderException(String msg, Exception root) { + super(msg, root); + } + + public PDJClassLoaderException(Exception root) { + super(root); + } +} diff --git a/src/java/com/e1/pdj/PDJError.java b/src/java/com/e1/pdj/PDJError.java new file mode 100644 index 0000000..7d89f1a --- /dev/null +++ b/src/java/com/e1/pdj/PDJError.java @@ -0,0 +1,15 @@ +package com.e1.pdj; + + +public class PDJError extends Error { + private static final long serialVersionUID = 1264000707984047887L; + + public PDJError(String msg) { + super(msg); + } + public PDJError(Throwable t) { + initCause(t); + } + public PDJError() { + } +} diff --git a/src/java/com/e1/pdj/PDJSystem.java b/src/java/com/e1/pdj/PDJSystem.java new file mode 100644 index 0000000..3d30a53 --- /dev/null +++ b/src/java/com/e1/pdj/PDJSystem.java @@ -0,0 +1,142 @@ +package com.e1.pdj; + +import com.cycling74.max.MaxSystem; + +import java.awt.Component; +import java.awt.Frame; +import java.awt.Toolkit; +import java.io.*; + +/** + * Startup class for pdj. + */ +public class PDJSystem { + private static int loaded = 0; + + public static PrintStream err; + + public static PrintStream out; + + /** + * Called by the pdj external when the JVM is initializing. + */ + public static void _init_system() { + if ( loaded == 1 ) + return; + linknative(); + initIO(); + } + + static void resolvRtJar() { + char ps = File.separatorChar; + String systemCpJar = System.getProperty("pdj.JAVA_HOME"); + if ( systemCpJar == null ) { + systemCpJar = System.getProperty("JAVA_HOME"); + if ( systemCpJar == null ) { + systemCpJar = System.getenv("JAVA_HOME"); + } + } + + System.setProperty("pdj.JAVA_HOME", systemCpJar); + GenericCompiler.rtJar = systemCpJar + ps + "jre" + ps + "lib" + ps + "rt.jar" + File.pathSeparator; + } + + /** + * Link the Java native classes + */ + static void linknative() { + String pdjHome = System.getProperty("pdj.home"); + + // this is a hack to be sure that statics of MaxSystem are loaded + // before everything + Class cls = MaxSystem.class; + + String osname = System.getProperty("os.name"); + + if ( osname.indexOf("Linux") != -1 ) { + // maps PD object as a JVM native library + Runtime.getRuntime().load(pdjHome + "/pdj.pd_linux"); + loaded = 1; + resolvRtJar(); + return; + } + + if ( osname.indexOf("Windows") != -1 ) { + // maps PD object as a JVM native library + Runtime.getRuntime().load(pdjHome + "/pdj.dll"); + loaded = 1; + resolvRtJar(); + return; + } + + if ( osname.indexOf("OS X") != -1 ) { + // maps PD object as a JVM native library + try { + Runtime.getRuntime().load(pdjHome + "/pdj.pd_darwin"); + } catch (UnsatisfiedLinkError e ) { + Runtime.getRuntime().load(pdjHome + "/pdj.pd_imac"); + } + loaded = 1; + + // this will initialize the AWT component in another thread + new Thread(new Runnable() { + public void run() { + Class clz = Component.class; + } + }).start(); + + GenericCompiler.rtJar = "/System/Library/Frameworks/JavaVM.framework/Classes/classes.jar:"; + + return; + } + + System.err.println("pdj: operating system type not found, the native link has not been made"); + } + + static boolean redirectIO() { + String prop = System.getProperty("pdj.redirect-pdio"); + + if ( prop == null ) + return true; + + if ( prop.charAt(0) == '0' ) + return false; + + if ( prop.equals("false") ) + return false; + + return true; + } + + static void initIO() { + if ( redirectIO() ) { + if ( System.getProperty("os.name").indexOf("Windows") == -1 ) { + out = new PrintStream(new ConsoleStream(), true); + err = new PrintStream(new ConsoleStream(), true); + } else { + out = new PrintStream(new ConsoleStreamWin32(), true); + err = new PrintStream(new ConsoleStreamWin32(), true); + } + System.setOut(out); + System.setErr(err); + } else { + out = System.out; + err = System.err; + } + } + + public static boolean isSystemPropertyTrue(String name) { + String value = System.getProperty(name); + + if ( value == null ) + return false; + + if ( value.toLowerCase().equals("true") ) + return true; + + if ( value.equals("1") ) + return true; + + return false; + } +} diff --git a/src/java/com/e1/pdj/PriorityQueue.java b/src/java/com/e1/pdj/PriorityQueue.java new file mode 100644 index 0000000..59a69a4 --- /dev/null +++ b/src/java/com/e1/pdj/PriorityQueue.java @@ -0,0 +1,68 @@ +package com.e1.pdj; + +import java.util.*; +import com.cycling74.max.Executable; + +public class PriorityQueue implements Runnable { + List list = new ArrayList(); + private Thread thread; + boolean tostop = false; + + public PriorityQueue(int priority) { + thread = new Thread(this); + + switch( priority ) { + case Thread.MIN_PRIORITY : + thread.setName("PriorityQueue:low"); + break; + case Thread.NORM_PRIORITY : + thread.setName("PriorityQueue:norm"); + break; + case Thread.MAX_PRIORITY : + thread.setName("PriorityQueue:max"); + break; + } + thread.setPriority(priority); + thread.setDaemon(true); + thread.start(); + } + + public void shutdown() { + synchronized(this) { + tostop = true; + notify(); + } + } + + public void run() { + Executable exec; + + while(true) { + try { + synchronized(this) { + if ( list.size() == 0 ) + wait(); + + if ( tostop ) + break; + exec = (Executable) list.remove(0); + } + try { + exec.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } catch (InterruptedException e) { + break; + } + } + } + + public void defer(Executable e) { + synchronized(this) { + list.add(e); + notify(); + } + } + +} diff --git a/src/java/com/e1/pdj/test/AtomTest.java b/src/java/com/e1/pdj/test/AtomTest.java new file mode 100644 index 0000000..c900ed8 --- /dev/null +++ b/src/java/com/e1/pdj/test/AtomTest.java @@ -0,0 +1,92 @@ +package com.e1.pdj.test; + +import junit.framework.TestCase; +import com.cycling74.max.Atom; + +public class AtomTest extends TestCase { + + public void testIsIn() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + assertEquals(Atom.isIn(Atom.newAtom("ok"), list), 1); + assertEquals(Atom.isIn(Atom.newAtom(5), list), 2); + assertEquals(Atom.isIn(Atom.newAtom(0), list), -1); + assertEquals(Atom.isIn(Atom.newAtom(1), list, 1 ,2), -1); + assertEquals(Atom.isIn(Atom.newAtom("ok"), list, 1 ,2), 1); + } + + public void testRemoveSome() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.removeSome(list, 1, 2); + assertEquals(1, test.length); + assertEquals(Atom.newAtom(1), test[0]); + } + + public void testReverse() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.reverse(list); + assertEquals(Atom.newAtom(5), test[0]); + assertEquals(Atom.newAtom("ok"), test[1]); + assertEquals(Atom.newAtom(1),test[2]); + } + + public void testRotate() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.rotate(list, 2); + assertEquals(Atom.newAtom("ok"), test[0]); + assertEquals(Atom.newAtom(5), test[1]); + assertEquals(Atom.newAtom(1), test[2]); + + test = Atom.rotate(list, 5); + assertEquals(Atom.newAtom("ok"), test[0]); + assertEquals(Atom.newAtom(5), test[1]); + assertEquals(Atom.newAtom(1), test[2]); + + test = Atom.rotate(list, 1); + assertEquals(Atom.newAtom(5), test[0]); + assertEquals(Atom.newAtom(1), test[1]); + assertEquals(Atom.newAtom("ok"), test[2]); + } + + public void testRemoveFirst() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.removeFirst(list, 2); + assertEquals(1, test.length); + assertEquals(Atom.newAtom(5), test[0]); + + test = Atom.removeFirst(list); + assertEquals(2, test.length); + assertEquals(Atom.newAtom("ok"), test[0]); + assertEquals(Atom.newAtom(5), test[1]); + } + + public void testRemoveLast() { + Atom[] list = new Atom[] { Atom.newAtom(1), Atom.newAtom("ok"), Atom.newAtom(5) }; + + Atom[] test = Atom.removeLast(list, 2); + assertEquals(1, test.length); + assertEquals(Atom.newAtom(1), test[0]); + + test = Atom.removeLast(list); + assertEquals(2, test.length); + assertEquals(Atom.newAtom(1), test[0]); + assertEquals(Atom.newAtom("ok"), test[1]); + } + + public void testUnion() { + assertTrue("union not implemented", false); + } + + public void testIntersection() { + assertTrue("intersection not implementated", false); + } + + public void testInt() { + Atom test = Atom.newAtom(0x90); + assertEquals(0x90, test.getInt()); + } +} diff --git a/src/java/com/e1/pdj/test/CallbackTest.java b/src/java/com/e1/pdj/test/CallbackTest.java new file mode 100644 index 0000000..4105479 --- /dev/null +++ b/src/java/com/e1/pdj/test/CallbackTest.java @@ -0,0 +1,10 @@ +package com.e1.pdj.test; + +import junit.framework.TestCase; + +public class CallbackTest extends TestCase { + + public void testSomething() { + + } +} diff --git a/src/java/com/e1/pdj/test/MaxQelemTest.java b/src/java/com/e1/pdj/test/MaxQelemTest.java new file mode 100644 index 0000000..a55fef9 --- /dev/null +++ b/src/java/com/e1/pdj/test/MaxQelemTest.java @@ -0,0 +1,7 @@ +package com.e1.pdj.test; + +import junit.framework.TestCase; + +public class MaxQelemTest extends TestCase { + +} |