Mercurial Hosting > nabble
changeset 68:00520880ad02
add fschmidt source
line wrap: on
line diff
--- a/build.sh Wed Nov 06 21:21:53 2024 -0700 +++ b/build.sh Sun Oct 05 17:24:15 2025 -0600 @@ -1,5 +1,8 @@ +#!/bin/bash set -e + . classpath.sh cd src +javac fschmidt/tools/Mmake.java java fschmidt.tools.Mmake make
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbArcana.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,38 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + + +public interface DbArcana { + public String quoteIdentifier(String identifier); + public String getLastSeqValFn(); + public String dateSub(String date,int n,String units); + public void setValue(PreparedStatement stmt,int idx,Object value) throws SQLException; + public String quote(String s); + public String stringEncode(String s); + public String quote(java.util.Date date); + public String stringEncode(java.util.Date date); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbDatabase.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,47 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; + + +public interface DbDatabase { + public void setProperty(String key,String value); + public DbArcana arcana(); + public Connection getConnection() throws SQLException; + public <K extends DbKey,V extends DbObject<K,V>> DbTable<K,V> newTable(String tableName,DbKeySetter<K> keySetter,DbObjectFactory<K,V> objectFactory); + public boolean isInTransaction(); + public void beginTransaction() throws SQLRuntimeException; + public void commitTransaction() throws SQLRuntimeException; + public void endTransaction() throws SQLRuntimeException; + public Map<String,Object> transactionMap(); + public void runBeforeCommit(Runnable r); + public void runAfterCommit(Runnable r); + public void ignoreRunnablesInThisTransaction(); + + public DbKeySetter<LongKey> newIdentityLongKeySetter(String fieldName); + public DbKeySetter<LongKey> newAssignedLongKeySetter(String fieldName); + public DbKeySetter<StringKey> newAssignedStringKeySetter(String fieldName); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbExpression.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,38 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + + +public class DbExpression { + private final String expr; + + public DbExpression(String expr) { + this.expr = expr; + } + + public String toString() { + return expr; + } + + public static final DbExpression NOW = new DbExpression("now()"); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbFinder.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,49 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + + +public class DbFinder { + + public static DbDatabase getDatabase(String url,String user,String password) { + return + new fschmidt.db.cache.DbDatabaseImpl( + new fschmidt.db.pool.DbDatabaseImpl( + getBaseDatabase(url,user,password) + ) + ) + ; + } + + public static DbDatabase getBaseDatabase(String url,String user,String password) { + if( url.startsWith("jdbc:mysql:") ) + return new fschmidt.db.mysql.DbDatabaseImpl(url,user,password); + if( url.startsWith("jdbc:postgresql:") ) + return new fschmidt.db.postgres.DbDatabaseImpl(url,user,password); + if( url.startsWith("jdbc:h2:") ) + return new fschmidt.db.h2.DbDatabaseImpl(url,user,password); + throw new IllegalArgumentException("unrecognized database type"); + } + + private DbFinder() {} // never +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbKey.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,26 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + + +public interface DbKey {}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbKeySetter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,40 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; + + +public interface DbKeySetter<K extends DbKey> { + public PreparedStatement prepareStatement(Connection con,K key,String sql,int keyIndex) + throws SQLException; + public PreparedStatement prepareStatement(Connection con,Collection<K> keys,String sql,DbArcana dbArcana) + throws SQLException; + public K refreshKeyAfterInsert(Connection con,DbRecord<K,? extends DbObject> record) throws SQLException; + public K getKey(ResultSet rs) throws SQLException; + public K getKey(ResultSet rs,String tableName) throws SQLException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbNull.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,63 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.util.Date; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; + + +public final class DbNull implements DbStorable { + public final int sqlType; + + public DbNull(int sqlType) { + this.sqlType = sqlType; + } + + public void setField(PreparedStatement stmt,int idx) throws SQLException { + stmt.setNull(idx,sqlType); + } + + public static final DbNull BOOLEAN = new DbNull(Types.BOOLEAN); + public static final DbNull STRING = new DbNull(Types.VARCHAR); + public static final DbNull TIMESTAMP = new DbNull(Types.TIMESTAMP); + public static final DbNull INTEGER = new DbNull(Types.INTEGER); + public static final DbNull FLOAT = new DbNull(Types.FLOAT); + + public static Object fix(boolean b) { + return b ? Boolean.TRUE : BOOLEAN; + } + public static Object fix(Boolean b) { + return b!=null ? b : BOOLEAN; + } + public static Object fix(String s) { + return s!=null ? s : STRING; + } + public static Object fix(Date date) { + return date!=null ? date : TIMESTAMP; + } + public static Object fix(int i) { + return i!=0 ? Integer.valueOf(i) : INTEGER; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbObject.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,28 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + + +public interface DbObject<K extends DbKey,V extends DbObject> { + public DbRecord<K,V> getDbRecord(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbObjectFactory.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,32 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.sql.ResultSet; +import java.sql.SQLException; + + +public interface DbObjectFactory<K extends DbKey,V extends DbObject> { + public V makeDbObject(K key,ResultSet rs,String tableName) + throws SQLException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbRecord.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,38 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.util.Map; + + +public interface DbRecord<K extends DbKey,V extends DbObject> { + public V getDbObject(); + public K getPrimaryKey(); + public void insert() throws SQLRuntimeException; + public void update() throws SQLRuntimeException; + public void delete() throws SQLRuntimeException; + public boolean isStale(); + public boolean isInDb(); + public DbTable<K,V> getDbTable(); + public Map<String,Object> fields(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbStorable.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + + +public interface DbStorable { + public void setField(PreparedStatement stmt,int idx) throws SQLException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbTable.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,47 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Map; + + +public interface DbTable<K extends DbKey,V extends DbObject> { + public String getTableName(); + public V findByPrimaryKey(K key); + public Map<K,V> findByPrimaryKey(Collection<K> keys); + public V getDbObject(ResultSet rs) throws SQLException; + public V getDbObject(ResultSet rs,String tableName) throws SQLException; + public DbRecord<K,V> newRecord(V obj,K key); // in db + public DbRecord<K,V> newRecord(V obj); // not in db + public DbDatabase getDbDatabase(); + public ListenerList<V> getPreInsertListeners(); + public ListenerList<V> getPostInsertListeners(); + public ListenerList<V> getPreUpdateListeners(); + public ListenerList<V> getPostUpdateListeners(); + public ListenerList<V> getPreDeleteListeners(); + public ListenerList<V> getPostDeleteListeners(); + public void uncache(K key); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/DbUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,89 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Date; + + +public final class DbUtils { + private DbUtils() {} // never + + public static <K extends DbKey,V extends DbObject<K,V>> V getGoodCopy(V obj) { + if( obj==null ) + return null; + DbRecord<K,V> record = obj.getDbRecord(); + return !record.isStale() ? obj + : record.getDbTable().findByPrimaryKey(record.getPrimaryKey()); + } + + public static <K extends DbKey,V extends DbObject<K,V>> void uncache(V obj) { + DbRecord<K,V> record = obj.getDbRecord(); + record.getDbTable().uncache(record.getPrimaryKey()); + } + + public static boolean isStale(DbObject obj) { + return obj==null || obj.getDbRecord().isStale(); + } + + public static boolean isStale(DbObject[] objs) { + if( objs==null ) + return true; + for( int i=0; i<objs.length; i++ ) { + if( isStale(objs[i]) ) + return true; + } + return false; + } + + public static boolean isEqual(DbObject o1,DbObject o2) { + DbRecord r1 = o1.getDbRecord(); + DbRecord r2 = o2.getDbRecord(); + if( !r1.isInDb() ) + return o1.equals(o2); + return r1.getPrimaryKey().equals(r2.getPrimaryKey()); + } + + public static Date getDate(ResultSet rs,String columnName) throws SQLException { + java.sql.Timestamp ts = rs.getTimestamp(columnName); + return ts==null ? null : new Date(ts.getTime()); + } + + public static void test(DbDatabase db) { + try { + Connection con = db.getConnection(); + try { + Statement stmt = con.createStatement(); + stmt.executeQuery("select 1"); + stmt.close(); + } finally { + con.close(); + } + } catch(SQLException e) { + throw new RuntimeException("test failed",e); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/Listener.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,28 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + + +public interface Listener<T> { + public void event(T source); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/ListenerList.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,56 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.util.List; +import java.util.ArrayList; + + +public final class ListenerList<T> implements Listener<T> { + private List<Listener<? super T>> listeners = new ArrayList<Listener<? super T>>(); + private Exception firstFired = null; + + public synchronized void add(Listener<? super T> listener) { + if( firstFired != null ) + throw new IllegalStateException("adding to ListenerList after already in use",firstFired); + listeners.add(listener); + } +/* + public synchronized void remove(Listener<? super T> listener) { + listeners.remove(listener); + } +*/ + public void event(T obj) { + synchronized(this) { + if( firstFired == null ) + firstFired = new Exception("first fired"); + } + for( Listener<? super T> listener : listeners ) { + listener.event(obj); + } + } + + public boolean isEmpty() { + return listeners.isEmpty(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/LongKey.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,48 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + + +public final class LongKey implements DbKey { + private final long id; + + public LongKey(long id) { + this.id = id; + } + + public long value() { + return id; + } + + public int hashCode() { + return (int)id; + } + + public boolean equals(Object obj) { + return obj instanceof LongKey && ((LongKey)obj).id==id; + } + + public String toString() { + return Long.toString(id); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/NoKey.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,6 @@ +package fschmidt.db; + + +public enum NoKey implements DbKey { + INSTANCE +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/NoKeySetter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,52 @@ +package fschmidt.db; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Connection; +import java.sql.Statement; +import java.util.Collection; +import java.util.Iterator; +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObject; +import fschmidt.db.NoKey; +import fschmidt.db.DbArcana; +import fschmidt.db.DbRecord; + + +public enum NoKeySetter implements DbKeySetter<NoKey> { + + INSTANCE; + + public PreparedStatement prepareStatement(Connection con,NoKey key,String sql,int keyIndex) + throws SQLException + { + PreparedStatement stmt = con.prepareStatement( + sql + ); + return stmt; + } + + public PreparedStatement prepareStatement(Connection con,Collection<NoKey> keys,String sql,DbArcana dbArcana) + throws SQLException + { + return prepareStatement(con,NoKey.INSTANCE,sql,0); + } + + public NoKey refreshKeyAfterInsert(Connection con,DbRecord<NoKey,? extends DbObject> record) throws SQLException { + return NoKey.INSTANCE; + } + + public NoKey getKey(ResultSet rs) + throws SQLException + { + return NoKey.INSTANCE; + } + + public NoKey getKey(ResultSet rs,String tableName) + throws SQLException + { + return NoKey.INSTANCE; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/SQLRuntimeException.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,41 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + +import java.sql.SQLException; + + +public class SQLRuntimeException extends RuntimeException { + + public SQLRuntimeException(SQLException e) { + super(e); + } + + public SQLRuntimeException(String msg,SQLException e) { + super(msg,e); + } + + public SQLException getSQLException() { + return (SQLException)getCause(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/StringKey.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,51 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db; + + +public final class StringKey implements DbKey { + private final String id; + + public StringKey(String id) { + if( id==null ) + throw new NullPointerException(); + this.id = id; + } + + public int hashCode() { + return id.hashCode(); + } + + public String value() { + return id; + } + + public boolean equals(Object obj) { + return obj!=null && obj instanceof StringKey + && ((StringKey)obj).id.equals(id); + } + + public String toString() { + return id; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/base/DbDatabaseImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,197 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.base; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Properties; +import fschmidt.db.DbObject; +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.SQLRuntimeException; +import fschmidt.db.LongKey; +import fschmidt.db.StringKey; +import fschmidt.db.DbStorable; +import fschmidt.db.extend.DbRecordExt; +import fschmidt.db.extend.DbDatabaseExt; +import fschmidt.db.extend.DbTableExt; +import fschmidt.db.extend.DbTransaction; + + +public abstract class DbDatabaseImpl extends DbDatabaseExt { + private final String url; + private final Properties props = new Properties(); + + public DbDatabaseImpl(String url,String user,String password) { + this.url = url; + props.setProperty("user",user); + props.setProperty("password",password); + } + + public void setProperty(String key,String value) { + props.setProperty(key,value); + } + + public Properties getProperties() { + return props; + } + + public Connection getConnection() + throws SQLException + { + return DriverManager.getConnection(url,props); + } + + public <K extends DbKey,V extends DbObject<K,V>> DbTableExt<K,V> newTable(DbDatabaseExt database,String tableName,DbKeySetter<K> keySetter,DbObjectFactory<K,V> objectFactory) { + return new DbTableImpl<K,V>(database,tableName,keySetter,objectFactory); + } + + public <K extends DbKey,V extends DbObject<K,V>> DbRecordExt<K,V> newRecord(DbTableExt<K,V> table,V obj,K key) { + return new DbRecordImpl<K,V>(table,obj,key); + } + + public <K extends DbKey,V extends DbObject<K,V>> DbRecordExt<K,V> newRecord(DbTableExt<K,V> table,V obj) { + return new DbRecordImpl<K,V>(table,obj); + } + + public boolean isInTransaction() { + throw new UnsupportedOperationException(); + } + + public void beginTransaction() + throws SQLRuntimeException + { + throw new UnsupportedOperationException(); + } + + public void commitTransaction() + throws SQLRuntimeException + { + throw new UnsupportedOperationException(); + } + + public void endTransaction() + throws SQLRuntimeException + { + throw new UnsupportedOperationException(); + } + + public void runBeforeCommit(Runnable r) { + r.run(); + } + + public void runAfterCommit(Runnable r) { + r.run(); + } + + public DbTransaction getTransaction() { + return null; + } + + public void ignoreRunnablesInThisTransaction() { + throw new UnsupportedOperationException(); + } + + + public DbKeySetter<LongKey> newIdentityLongKeySetter(String fieldName) { + return new LongKeySetter(fieldName); + } + + public DbKeySetter<LongKey> newAssignedLongKeySetter(String fieldName) { + return new LongForeignKeySetter(fieldName); + } + + public DbKeySetter<StringKey> newAssignedStringKeySetter(String fieldName) { + return new StringKeySetter(fieldName); + } + + public void setValue(PreparedStatement stmt,int idx,Object value) + throws SQLException + { + if( value==null ) { + throw new SQLException("null value"); + } else if( value instanceof DbStorable ) { + ((DbStorable)value).setField(stmt,idx); + } else if( value instanceof Character ) { + stmt.setString(idx,value.toString()); + } else if( value instanceof java.util.Date ) { + stmt.setTimestamp(idx,new java.sql.Timestamp(((java.util.Date)value).getTime())); + } else if( (new byte[0]).getClass().isInstance(value) ) { + stmt.setBytes(idx,(byte[])value); + } else if( value instanceof Boolean ) { + stmt.setBoolean( idx, ((Boolean)value).booleanValue() ); + } else if( value instanceof Float ) { + stmt.setFloat( idx, ((Float)value).floatValue() ); + } else { + stmt.setObject(idx,value); + } + } + + + + + public String quote(String s) { + return "'" + stringEncode(s) + "'"; + } + + public String stringEncode(String s) { + StringBuilder buf = new StringBuilder(); + int n = s.length(); + for( int i=0; i<n; i++ ) { + char c = s.charAt(i); + switch(c) { + case '\n': + buf.append("\\n"); + break; + case '\t': + buf.append("\\t"); + break; + case '\r': + buf.append("\\r"); + break; + case '\'': + buf.append("\\'"); + break; + default: + buf.append(c); + } + } + return buf.toString(); + } + + private static final DateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public String quote(java.util.Date date) { + return "'" + stringEncode(date) + "'"; + } + + public String stringEncode(java.util.Date date) { + return dateFmt.format(date); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/base/DbRecordImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,333 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.base; + +import java.sql.SQLException; +import java.sql.PreparedStatement; +import java.sql.Connection; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import fschmidt.db.DbTable; +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObject; +import fschmidt.db.DbRecord; +import fschmidt.db.SQLRuntimeException; +import fschmidt.db.DbExpression; +import fschmidt.db.LongKey; +import fschmidt.db.DbUtils; +import fschmidt.db.DbArcana; +import fschmidt.db.ListenerList; +import fschmidt.db.extend.DbRecordExt; +import fschmidt.db.extend.DbTableExt; +import fschmidt.db.extend.DbTransaction; + + +final class DbRecordImpl<K extends DbKey,V extends DbObject<K,V>> implements DbRecordExt<K,V> { + private static final Logger logger = LoggerFactory.getLogger(DbRecordImpl.class); + + public static boolean hasStackTrace = false; + private final V obj; + private K key; + private final DbTableExt<K,V> table; + private Map<String,Object> fields = new HashMap<String,Object>(); + private boolean isInDb; + private boolean isStale = false; +// private final long whenLoaded = System.currentTimeMillis(); + private DbTransaction trans; + + public DbRecordImpl(DbTableExt<K,V> table,V obj,K key) { + this.table = table; + this.obj = obj; + this.key = key; + this.isInDb = true; + this.trans = table.getDbDatabaseExt().getTransaction(); + } + + public DbRecordImpl(DbTableExt<K,V> table,V obj) { + this.table = table; + this.obj = obj; + this.key = null; + this.isInDb = false; + this.trans = table.getDbDatabaseExt().getTransaction(); + } + + public V getDbObject() { + return obj; + } + + public K getPrimaryKey() { + return key; + } + + public DbTable<K,V> getDbTable() { + return table; + } + + public Map<String,Object> fields() { + return fields; + } + +/* + public long whenLoaded() { + return whenLoaded; + } +*/ + private void checkTrans() { + if( trans != table.getDbDatabaseExt().getTransaction() ) { + if( trans==null ) + throw new IllegalStateException("record read outside of a transaction cannot be updated inside a transaction"); + else + throw new IllegalStateException("transaction mismatch"); + } + } + + public void insert() { + if( isInDb() ) + throw new IllegalStateException("already in database"); + try { + ListenerList<V> list = table.getPreInsertListeners(); + list.event(obj); + store(); + this.trans = table.getDbDatabaseExt().getTransaction(); + final Map<String,Object> updatedFields = fields; + final Exception ex = hasStackTrace ? new Exception("in "+Thread.currentThread()) : null; + table.getDbDatabaseExt().runBeforeCommit( new Runnable() { + public void run() { + ListenerList<V> list = table.getPostInsertListeners(); + if( !list.isEmpty() ) { + V obj2 = DbUtils.getGoodCopy(obj); + if( obj2==null ) { + DbRecord<K,V> rec = obj.getDbRecord(); + K key = rec.getPrimaryKey(); + if( ex != null ) + logger.error("insert null in "+Thread.currentThread()+" table="+table+" key="+key, ex); + throw new NullPointerException("in "+Thread.currentThread()+" table="+table+" key="+key); + } + DbRecordImpl rec2 = (DbRecordImpl)obj2.getDbRecord(); + try { + list.event(obj2); + } finally { + updatedFields.clear(); + } + } + } + } ); + } finally { + fields = new HashMap<String,Object>(); + makeStale(); + } + } + + public void update() { + checkTrans(); + if( !isInDb() ) + throw new IllegalStateException("not in database"); + try { + ListenerList<V> list = table.getPreUpdateListeners(); + list.event(obj); + if( fields.isEmpty() ) { + table.uncache(key); + } else { + store(); + } + final Map<String,Object> updatedFields = fields; + table.getDbDatabaseExt().runBeforeCommit( new Runnable() { + public void run() { + ListenerList<V> list = table.getPostUpdateListeners(); + if( !list.isEmpty() && isInDb ) { + V obj2 = DbUtils.getGoodCopy(obj); + if( obj2==null ) + return; // presumably deleted later in transaction + DbRecordImpl rec2 = (DbRecordImpl)obj2.getDbRecord(); + try { + list.event(obj2); + } finally { + updatedFields.clear(); + } + } + } + } ); + } finally { + fields = new HashMap<String,Object>(); + } + } + + private void store() { + StringBuilder buf = new StringBuilder(); + try { + DbArcana arcana = table.getDbDatabase().arcana(); + int n = 0; + if( isInDb() ) { // update + buf.append( "update " ); + buf.append( table.getTableName() ); + buf.append( " set " ); + boolean first = true; + for( Map.Entry<String,Object> entry : fields.entrySet() ) { + if( first ) { + first = false; + } else { + buf.append( ',' ); + } + buf.append( arcana.quoteIdentifier(entry.getKey()) ); + if( entry.getValue() instanceof DbExpression ) { + buf.append( '=' ); + buf.append( entry.getValue() ); + } else { + buf.append( "=?" ); + n++; + } + } + } else { // insert + buf.append( "insert into " ); + buf.append( table.getTableName() ); + if( fields.isEmpty() ) { + buf.append( " DEFAULT VALUES" ); + } else { + buf.append( " (" ); + boolean first = true; + for( Map.Entry<String,Object> entry : fields.entrySet() ) { + if( first ) { + first = false; + } else { + buf.append( ',' ); + } + buf.append( arcana.quoteIdentifier(entry.getKey()) ); + } + buf.append( ") values (" ); + first = true; + for( Map.Entry<String,Object> entry : fields.entrySet() ) { + if( first ) { + first = false; + } else { + buf.append(','); + } + if( entry.getValue() instanceof DbExpression ) { + buf.append( entry.getValue() ); + } else { + buf.append( '?' ); + n++; + } + } + buf.append( ")" ); + } + } + Connection con = table.getDbDatabaseExt().getConnection(); + try { + DbKeySetter<K> keySetter = table.getKeySetter(); + PreparedStatement stmt; + if( isInDb() ) { + stmt = keySetter.prepareStatement( + con, key, buf.toString(), 1+n + ); + } else { + stmt = con.prepareStatement( buf.toString() ); + } + int idx = 1; + for( Map.Entry<String,Object> entry : fields.entrySet() ) { + Object value = entry.getValue(); + if( value instanceof DbExpression ) + continue; + try { + arcana.setValue(stmt,idx++,value); + } catch(SQLException e) { + throw new SQLRuntimeException("field="+entry.getKey()+" value="+value,e); + } + } + stmt.executeUpdate(); + boolean wasInDb = isInDb; + isInDb = true; + stmt.close(); + if( !wasInDb ) { + key = keySetter.refreshKeyAfterInsert(con,this); + } + } finally { + try { + con.close(); + } catch(SQLException e) { + logger.error("couldn't close connection",e); + throw e; + } catch(RuntimeException e) { + logger.error("couldn't close connection",e); + throw e; + } + } + } catch(SQLException e) { + throw new SQLRuntimeException("key = "+key+" sql = "+buf.toString(),e); + } finally { + table.uncache(key); + } + } + + public void delete() { + checkTrans(); + ListenerList<V> list = table.getPreDeleteListeners(); + list.event(obj); + if( !isInDb() ) + throw new IllegalStateException("not in database"); + try { + Connection con = table.getDbDatabaseExt().getConnection(); + PreparedStatement stmt = table.getKeySetter().prepareStatement( + con, key, "delete from "+table.getTableName(), 1 + ); + stmt.executeUpdate(); + isInDb = false; + stmt.close(); + con.close(); +/* + table.getDbDatabaseExt().runBeforeCommit( new Runnable() { + public void run() { + table.getPostDeleteListeners().event(obj); + } + } ); +*/ + } catch(SQLException e) { + throw new SQLRuntimeException("key = "+key,e); + } finally { + table.uncache(key); + fields.clear(); + } + table.getDbDatabaseExt().runBeforeCommit( new Runnable() { + public void run() { + ListenerList<V> list = table.getPostDeleteListeners(); + list.event(obj); + } + } ); + } + + public boolean isStale() { + return isStale || (isInDb || trans!=null) && trans != table.getDbDatabaseExt().getTransaction(); + } + + public void makeStale() { + isStale = true; + } + + public boolean isInDb() { + return isInDb; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/base/DbTableImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,159 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.base; + +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObject; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.ListenerList; +import fschmidt.db.SQLRuntimeException; +import fschmidt.db.extend.DbDatabaseExt; +import fschmidt.db.extend.DbTableExt; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + + +final class DbTableImpl<K extends DbKey,V extends DbObject<K,V>> extends DbTableExt<K,V> { + private final String tableName; + private final DbKeySetter<K> keySetter; + private final DbObjectFactory<K,V> objFactory; + + DbTableImpl(DbDatabaseExt database,String tableName,DbKeySetter<K> keySetter,DbObjectFactory<K,V> objFactory) { + super(database); + this.tableName = tableName; + this.keySetter = keySetter; + this.objFactory = objFactory; + } + + public String getTableName() { + return tableName; + } + + public DbKeySetter<K> getKeySetter() { + return keySetter; + } + + public final V findByPrimaryKey(K key) { + try { + Connection con = database.getConnection(); + try { + PreparedStatement stmt = keySetter.prepareStatement( + con, key, "select * from "+tableName, 1 + ); + ResultSet rs = stmt.executeQuery(); + V obj = null; + if( rs.next() ) + obj = getDbObject(key,rs); + stmt.close(); + return obj; + } finally { + con.close(); + } + } catch(SQLException e) { + throw new SQLRuntimeException("key = "+key,e); + } + } + + public final Map<K,V> findByPrimaryKey(Collection<K> keys) { + Map<K,V> map = new HashMap<K,V>(); + try { + Connection con = database.getConnection(); + try { + PreparedStatement stmt = keySetter.prepareStatement( + con, keys, "select * from "+tableName, getDbDatabase().arcana() + ); + ResultSet rs = stmt.executeQuery(); + while( rs.next() ) { + K key = keySetter.getKey(rs); + map.put( key, getDbObject(key,rs) ); + } + stmt.close(); + } finally { + con.close(); + } + } catch(SQLException e) { + throw new SQLRuntimeException("keys = "+keys,e); + } + return map; + } + + public V getDbObject(K key,ResultSet rs,String tableName) + throws SQLException + { + return objFactory.makeDbObject(key,rs,tableName); + } + + + private ListenerList<V> preInsertListeners; + private ListenerList<V> preUpdateListeners; + private ListenerList<V> preDeleteListeners; + private ListenerList<V> postInsertListeners; + private ListenerList<V> postUpdateListeners; + private ListenerList<V> postDeleteListeners; + + public synchronized ListenerList<V> getPreInsertListeners() { + if (preInsertListeners == null) + preInsertListeners = new ListenerList<V>(); + return preInsertListeners; + } + + public synchronized ListenerList<V> getPreUpdateListeners() { + if (preUpdateListeners == null) + preUpdateListeners = new ListenerList<V>(); + return preUpdateListeners; + } + + public synchronized ListenerList<V> getPreDeleteListeners() { + if (preDeleteListeners == null) + preDeleteListeners = new ListenerList<V>(); + return preDeleteListeners; + } + + public synchronized ListenerList<V> getPostInsertListeners() { + if (postInsertListeners == null) + postInsertListeners = new ListenerList<V>(); + return postInsertListeners; + } + + public synchronized ListenerList<V> getPostUpdateListeners() { + if (postUpdateListeners == null) + postUpdateListeners = new ListenerList<V>(); + return postUpdateListeners; + } + + public synchronized ListenerList<V> getPostDeleteListeners() { + if (postDeleteListeners == null) + postDeleteListeners = new ListenerList<V>(); + return postDeleteListeners; + } + + public void uncache(DbKey key) {} + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/base/LongForeignKeySetter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,46 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.base; + +import java.sql.Connection; +import java.sql.SQLException; +import fschmidt.db.DbKey; +import fschmidt.db.DbObject; +import fschmidt.db.LongKey; +import fschmidt.db.DbRecord; + + +final class LongForeignKeySetter extends LongKeySetter { + + public LongForeignKeySetter(String fieldName) { + super(fieldName); + } + + public LongKey refreshKeyAfterInsert(Connection con,DbRecord<LongKey,? extends DbObject> record) + throws SQLException + { +// return record.getPrimaryKey(); + Long id = (Long)record.fields().get(fieldName); + return new LongKey(id.longValue()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/base/LongKeySetter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,101 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.base; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Connection; +import java.sql.Statement; +import java.util.Collection; +import java.util.Iterator; +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObject; +import fschmidt.db.LongKey; +import fschmidt.db.DbArcana; +import fschmidt.db.DbRecord; + + +class LongKeySetter implements DbKeySetter<LongKey> { + protected final String fieldName; + + public LongKeySetter(String fieldName) { + this.fieldName = fieldName; + } + + public PreparedStatement prepareStatement(Connection con,LongKey key,String sql,int keyIndex) + throws SQLException + { + PreparedStatement stmt = con.prepareStatement( + sql + " where " + fieldName + "=?" + ); + stmt.setLong(keyIndex,((LongKey)key).value()); + return stmt; + } + + public PreparedStatement prepareStatement(Connection con,Collection<LongKey> keys,String sql,DbArcana dbArcana) + throws SQLException + { + StringBuilder buf = new StringBuilder(); + buf.append( sql ); + buf.append( " where " ); + buf.append( fieldName ); + buf.append( " in (" ); + Iterator<LongKey> iter = keys.iterator(); + buf.append( iter.next().value() ); + while( iter.hasNext() ) { + buf.append( ',' ).append( iter.next().value() ); + } + buf.append( ")" ); + return con.prepareStatement(buf.toString()); + } + + public LongKey refreshKeyAfterInsert(Connection con,DbRecord<LongKey,? extends DbObject> record) throws SQLException { + if( record.fields().containsKey(fieldName) ) { + Long key = (Long)record.fields().get(fieldName); + return new LongKey(key); + } + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( + "select " + record.getDbTable().getDbDatabase().arcana().getLastSeqValFn() + " as id" + ); + rs.next(); + LongKey key = new LongKey( rs.getLong("id") ); + rs.close(); + stmt.close(); + return key; + } + + public LongKey getKey(ResultSet rs) + throws SQLException + { + return new LongKey(rs.getLong(fieldName)); + } + + public LongKey getKey(ResultSet rs,String tableName) + throws SQLException + { + return new LongKey(rs.getLong(tableName+'.'+fieldName)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/base/StringKeySetter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,91 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.base; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.util.Collection; +import java.util.Iterator; +import fschmidt.db.DbKey; +import fschmidt.db.DbObject; +import fschmidt.db.StringKey; +import fschmidt.db.DbArcana; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbRecord; + + +final class StringKeySetter implements DbKeySetter<StringKey> { + private final String fieldName; + + public StringKeySetter(String fieldName) { + this.fieldName = fieldName; + } + + public PreparedStatement prepareStatement(Connection con,StringKey key,String sql,int keyIndex) + throws SQLException + { + PreparedStatement stmt = con.prepareStatement( + sql + " where " + fieldName + "=?" + ); + stmt.setString(keyIndex,key.value()); + return stmt; + } + + public PreparedStatement prepareStatement(Connection con,Collection<StringKey> keys,String sql,DbArcana dbArcana) + throws SQLException + { + StringBuilder buf = new StringBuilder(); + buf.append( sql ); + buf.append( " where " ); + buf.append( fieldName ); + buf.append( " in (" ); + Iterator<StringKey> iter = keys.iterator(); + buf.append( dbArcana.quote(iter.next().value()) ); + while( iter.hasNext() ) { + buf.append( ',' ).append( dbArcana.quote(iter.next().value()) ); + } + buf.append( ")" ); + return con.prepareStatement(buf.toString()); + } + + public StringKey refreshKeyAfterInsert(Connection con,DbRecord<StringKey,? extends DbObject> record) throws SQLException { +// throw new RuntimeException(); +// return record.getPrimaryKey(); // ? + String id = (String)record.fields().get(fieldName); + return new StringKey(id); + } + + public StringKey getKey(ResultSet rs) + throws SQLException + { + return new StringKey(rs.getString(fieldName)); + } + + public StringKey getKey(ResultSet rs,String tableName) + throws SQLException + { + return new StringKey(rs.getString(tableName+'.'+fieldName)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/cache/DbDatabaseImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,55 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.cache; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.Collections; +import fschmidt.db.DbDatabase; +import fschmidt.db.DbObject; +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.extend.DbDatabaseExt; +import fschmidt.db.extend.DbTableExt; +import fschmidt.db.extend.DbRecordExt; +import fschmidt.db.extend.FilterDatabase; + + +public final class DbDatabaseImpl extends FilterDatabase { + + public DbDatabaseImpl(DbDatabase database) { + super((DbDatabaseExt)database); + } + + public <K extends DbKey,V extends DbObject<K,V>> DbTableExt<K,V> newTable(DbDatabaseExt database,String tableName,DbKeySetter<K> keySetter,DbObjectFactory<K,V> objFactory) { + return new DbTableImpl<K,V>(this.database.newTable(database,tableName,keySetter,objFactory),this); + } + + public static void clearCache() { + DbTableImpl.clearCache(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/cache/DbTableImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,261 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.cache; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; +import java.util.HashMap; +import java.util.Set; +import java.util.HashSet; +import java.util.Collections; +import java.util.Collection; +import java.util.NoSuchElementException; +import fschmidt.db.util.Unique; +import fschmidt.db.util.WeakCacheMap; +import fschmidt.db.DbObject; +import fschmidt.db.DbKey; +import fschmidt.db.DbRecord; +import fschmidt.db.extend.DbRecordExt; +import fschmidt.db.extend.DbTableExt; +import fschmidt.db.extend.DbDatabaseExt; +import fschmidt.db.extend.DbTransaction; +import fschmidt.db.extend.FilterTable; + + +final class DbTableImpl<K extends DbKey,V extends DbObject<K,V>> extends FilterTable<K,V> { + private static final DbObject NULL0 = new DbObject(){ + public DbRecord getDbRecord() { throw new RuntimeException(); } + }; + + @SuppressWarnings("unchecked") + private final V NULL = (V)NULL0; // hack + + private static class CacheKey { + private final DbTableImpl table; + final Object key; + + CacheKey(DbTableImpl table,Object key) { + this.table = table; + this.key = key; + } + + @Override public boolean equals(Object obj) { + if( obj == this ) + return true; + if( !(obj instanceof DbTableImpl.CacheKey) ) + return false; + CacheKey ck = (CacheKey)obj; + return ck.table == table && ck.key.equals(key); + } + + @Override public int hashCode() { + return table.hashCode() + key.hashCode(); + } + + } + + private static final Map<CacheKey,DbObject> cache0 = Collections.synchronizedMap(new WeakCacheMap<CacheKey,DbObject>()); + private final Unique<K> unique = new Unique<K>(); + private final DbDatabaseImpl database; + + DbTableImpl(DbTableExt<K,V> table,DbDatabaseImpl database) { + super(database,table); + this.database = database; + } + + private Map<CacheKey,DbObject> transCache() { + Map<String,Object> transMap = database.transactionMap(); + @SuppressWarnings("unchecked") + Map<CacheKey,DbObject> transCache = (Map<CacheKey,DbObject>)transMap.get("transCache"); + if( transCache == null ) { + transCache = new WeakCacheMap<CacheKey,DbObject>(); + transMap.put( "transCache", transCache ); + } + return transCache; + } + + private final boolean isStale(DbObject obj) { + if( obj==null ) + return true; + if( obj==NULL ) + return false; +/* + if( database.isInTransaction() ) { + return transCache().containsKey(obj.getDbRecord().getPrimaryKey()); + } else { + return obj.getDbRecord().isStale(); + } +*/ + return obj.getDbRecord().isStale(); + } + + public final V findByPrimaryKey(K key) { + CacheKey ck = new CacheKey(this,key); + if( database.isInTransaction() ) { + Map<CacheKey,DbObject> cache = transCache(); + @SuppressWarnings("unchecked") + V obj = (V)cache.get(ck); + if( obj==null ) { + obj = table.findByPrimaryKey(key); + if( obj==null ) + obj = NULL; + cache.put(ck,obj); + } + return obj==NULL ? null : obj; + } + Map<CacheKey,DbObject> cache = cache0; + key = unique.get(key); + try { + synchronized(key) { + @SuppressWarnings("unchecked") + V obj = (V)cache.get(ck); + if( !isStale(obj) ) + return obj==NULL ? null : obj; + obj = table.findByPrimaryKey(key); + if( !isStale( cache.put( ck, obj==null ? NULL : obj ) ) ) + throw new RuntimeException(); + return obj; + } + } finally { + unique.free(key); + } + } + + public final Map<K,V> findByPrimaryKey(Collection<K> keys) { + Map<K,V> map = new HashMap<K,V>(); + Set<K> set = new HashSet<K>(); + Map<CacheKey,DbObject> cache = database.isInTransaction() ? transCache() : cache0; + boolean inTrans = database.isInTransaction(); + for( K key : keys ) { + CacheKey ck = new CacheKey(this,key); + @SuppressWarnings("unchecked") + V obj = (V)cache.get(ck); + if( obj==null || !inTrans && isStale(obj) ) { + set.add(key); + } else { + map.put( key, obj==NULL ? null : obj ); + } + } + if( !set.isEmpty() ) { + Map<K,V> objs = table.findByPrimaryKey(set); + for( K key : set ) { + CacheKey ck = new CacheKey(this,key); + if( inTrans ) { + if( cache.get(ck) != null ) + throw new RuntimeException(); + V obj = table.findByPrimaryKey(key); + if( obj==null ) + obj = NULL; + cache.put(ck,obj); + map.put( key, obj==NULL ? null : obj ); + } else { + key = unique.get(key); + try { + synchronized(key) { + @SuppressWarnings("unchecked") + V obj = (V)cache.get(ck); + if( isStale(obj) ) { + obj = objs.get(key); + if( obj == null ) + obj = NULL; + if( !isStale( cache.put(ck,obj) ) ) + throw new RuntimeException(); + } + map.put( key, obj==NULL ? null : obj ); + } + } finally { + unique.free(key); + } + } + } + } + return map; + } + + public V getDbObject(K key,ResultSet rs,String tableName) + throws SQLException + { + CacheKey ck = new CacheKey(this,key); + if( database.isInTransaction() ) { + Map<CacheKey,DbObject> cache = transCache(); + @SuppressWarnings("unchecked") + V obj = (V)cache.get(ck); + if( obj==null ) { + obj = table.getDbObject(key,rs,tableName); + if( obj==null ) + obj = NULL; + cache.put(ck,obj); + } + return obj==NULL ? null : obj; + } + Map<CacheKey,DbObject> cache = cache0; + key = unique.get(key); + try { + synchronized(key) { + @SuppressWarnings("unchecked") + V obj = (V)cache.get(ck); + if( !isStale(obj) ) + return obj==NULL ? null : obj; + obj = table.getDbObject(key,rs,tableName); + if( !isStale( cache.put( ck, obj==null ? NULL : obj ) ) ) + throw new RuntimeException(); + return obj; + } + } finally { + unique.free(key); + } + } + + public void uncache(final K key) { + if( key == null ) + return; + final CacheKey ck = new CacheKey(this,key); + if( database.isInTransaction() ) { + doUncache(ck,transCache()); + } + database.runJustAfterCommit( new Runnable() { + public void run() { + doUncache(ck,cache0); + } + } ); + } + + private static void doUncache(CacheKey ck,Map<CacheKey,DbObject> cache) { + DbObject obj = cache.remove(ck); + if( obj!=null && obj!=NULL0 ) { + DbRecordExt rec = (DbRecordExt)obj.getDbRecord(); + rec.makeStale(); + } + } + + static void clearCache() { + while( !cache0.isEmpty() ) { + CacheKey[] a = cache0.keySet().toArray(new CacheKey[0]); + for( CacheKey ck : a ) { + doUncache( ck, cache0 ); + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/examples/Example.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,97 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.examples; + +import java.sql.ResultSet; +import java.sql.SQLException; +import fschmidt.db.DbObject; +import fschmidt.db.DbRecord; +import fschmidt.db.DbKey; +import fschmidt.db.LongKey; +import fschmidt.db.DbFinder; +import fschmidt.db.DbDatabase; +import fschmidt.db.DbTable; +import fschmidt.db.DbObjectFactory; + + +public class Example implements DbObject<LongKey,Example> { + static final String url = "jdbc:mysql://localhost/talk"; + static final String user = ""; + static final String password = ""; + static DbDatabase db = DbFinder.getDatabase(url,user,password); + + private final DbRecord<LongKey,Example> record; + private String text; + + private Example(LongKey key,ResultSet rs) + throws SQLException + { + record = table.newRecord(this,key); + text = rs.getString("text"); + } + + public Example(String text) { + record = table.newRecord(this); + this.text = text; + } + + public DbRecord<LongKey,Example> getDbRecord() { + return record; + } + + public long getId() { + return record.getPrimaryKey().value(); + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + record.fields().put("text",text); + } + + static DbTable<LongKey,Example> table = db.newTable( "example", db.newIdentityLongKeySetter("example_id"), + new DbObjectFactory<LongKey,Example>() { + public Example makeDbObject(LongKey key,ResultSet rs,String tableName) + throws SQLException + { + return new Example(key,rs); + } + } + ); + + public static Example findExample(long id) { + return table.findByPrimaryKey(new LongKey(id)); + } + + public static void main(String[] args) throws Exception { + Example example = new Example("hello world"); + example.getDbRecord().insert(); + long id = example.getId(); + + example = findExample(id); + System.out.println(example.getText()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/extend/DbDatabaseExt.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,63 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.extend; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; +import fschmidt.db.DbDatabase; +import fschmidt.db.DbObject; +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.DbTable; + + +public abstract class DbDatabaseExt implements DbDatabase { + public abstract <K extends DbKey,V extends DbObject<K,V>> DbRecordExt<K,V> newRecord(DbTableExt<K,V> table,V obj,K key); + public abstract <K extends DbKey,V extends DbObject<K,V>> DbRecordExt<K,V> newRecord(DbTableExt<K,V> table,V obj); + public abstract <K extends DbKey,V extends DbObject<K,V>> DbTableExt<K,V> newTable(DbDatabaseExt database,String tableName,DbKeySetter<K> keySetter,DbObjectFactory<K,V> objectFactory); + public abstract DbTransaction getTransaction(); + + public final <K extends DbKey,V extends DbObject<K,V>> DbTable<K,V> newTable(String tableName,DbKeySetter<K> keySetter,DbObjectFactory<K,V> objectFactory) { + return newTable(this,tableName,(DbKeySetter<K>)keySetter,objectFactory); + } + + public final Map<String,Object> transactionMap() { + DbTransaction trans = getTransaction(); + if( trans==null ) + return null; + if( trans.map==null ) + trans.map = new HashMap<String,Object>(); + return trans.map; + } + + + public void runJustAfterCommit(Runnable r) { + runAfterCommit(r); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/extend/DbRecordExt.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,33 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.extend; + +import java.util.Map; +import fschmidt.db.DbRecord; +import fschmidt.db.DbKey; +import fschmidt.db.DbObject; + + +public interface DbRecordExt<K extends DbKey,V extends DbObject> extends DbRecord<K,V> { + public void makeStale(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/extend/DbTableExt.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,83 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.extend; + +import java.sql.ResultSet; +import java.sql.SQLException; +import fschmidt.db.DbTable; +import fschmidt.db.DbDatabase; +import fschmidt.db.DbObject; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbKey; +import fschmidt.db.DbRecord; + + +public abstract class DbTableExt<K extends DbKey,V extends DbObject<K,V>> implements DbTable<K,V> { + protected final DbDatabaseExt database; + + protected DbTableExt(DbDatabaseExt database) { + this.database = database; + } + + public abstract DbKeySetter<K> getKeySetter(); + public abstract V getDbObject(K key,ResultSet rs,String tableName) throws SQLException; + + public final DbDatabaseExt getDbDatabaseExt() { + return database; + } + + public final DbDatabase getDbDatabase() { + return getDbDatabaseExt(); + } + + public final DbRecord<K,V> newRecord(V obj,K key) { + return getDbDatabaseExt().newRecord(this,obj,key); + } + + public final DbRecord<K,V> newRecord(V obj) { + return getDbDatabaseExt().newRecord(this,obj); + } + + public final V getDbObject(K key,ResultSet rs) + throws SQLException + { + return getDbObject(key,rs,getTableName()); + } + + public final V getDbObject(ResultSet rs) + throws SQLException + { + return getDbObject(getKeySetter().getKey(rs),rs); + } + + public final V getDbObject(ResultSet rs,String tableName) + throws SQLException + { + return getDbObject(getKeySetter().getKey(rs,tableName),rs,tableName); + } + + public String toString() { + return getTableName(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/extend/DbTransaction.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.extend; + +import java.util.Map; +import java.util.HashMap; + + +public final class DbTransaction { + Map<String,Object> map; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/extend/FilterDatabase.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,125 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.extend; + +import java.sql.Connection; +import java.sql.SQLException; +import fschmidt.db.DbObject; +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.DbArcana; +import fschmidt.db.SQLRuntimeException; +import fschmidt.db.LongKey; +import fschmidt.db.StringKey; + + +public class FilterDatabase extends DbDatabaseExt { + protected final DbDatabaseExt database; + + public FilterDatabase(DbDatabaseExt database) { + this.database = database; + } + + public void setProperty(String key,String value) { + database.setProperty(key,value); + } + + public DbArcana arcana() { + return database.arcana(); + } + + public <K extends DbKey,V extends DbObject<K,V>> DbTableExt<K,V> newTable(DbDatabaseExt database,String tableName,DbKeySetter<K> keySetter,DbObjectFactory<K,V> objFactory) { + return this.database.newTable(database,tableName,keySetter,objFactory); + } + + public <K extends DbKey,V extends DbObject<K,V>> DbRecordExt<K,V> newRecord(DbTableExt<K,V> table,V obj,K key) { + return database.newRecord(table,obj,key); + } + + public <K extends DbKey,V extends DbObject<K,V>> DbRecordExt<K,V> newRecord(DbTableExt<K,V> table,V obj) { + return database.newRecord(table,obj); + } + + public Connection getConnection() + throws SQLException + { + return database.getConnection(); + } + + public void runBeforeCommit(Runnable r) { + database.runBeforeCommit(r); + } + + public void runAfterCommit(Runnable r) { + database.runAfterCommit(r); + } + + public void runJustAfterCommit(Runnable r) { + database.runJustAfterCommit(r); + } + + public void ignoreRunnablesInThisTransaction() { + database.ignoreRunnablesInThisTransaction(); + } + + public boolean isInTransaction() { + return database.isInTransaction(); + } + + public void beginTransaction() + throws SQLRuntimeException + { + database.beginTransaction(); + } + + public void commitTransaction() + throws SQLRuntimeException + { + database.commitTransaction(); + } + + public void endTransaction() + throws SQLRuntimeException + { + database.endTransaction(); + } + + public DbTransaction getTransaction() { + return database.getTransaction(); + } + + + public DbKeySetter<LongKey> newIdentityLongKeySetter(String fieldName) { + return database.newIdentityLongKeySetter(fieldName); + } + + public DbKeySetter<LongKey> newAssignedLongKeySetter(String fieldName) { + return database.newAssignedLongKeySetter(fieldName); + } + + public DbKeySetter<StringKey> newAssignedStringKeySetter(String fieldName) { + return database.newAssignedStringKeySetter(fieldName); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/extend/FilterTable.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,71 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.extend; + +import fschmidt.db.ListenerList; +import fschmidt.db.DbKey; +import fschmidt.db.DbKeySetter; +import fschmidt.db.DbObject; + + +public abstract class FilterTable<K extends DbKey,V extends DbObject<K,V>> extends DbTableExt<K,V> { + protected final DbTableExt<K,V> table; + + protected FilterTable(DbDatabaseExt database,DbTableExt<K,V> table) { + super(database); + this.table = table; + } + + public String getTableName() { + return table.getTableName(); + } + + public DbKeySetter<K> getKeySetter() { + return table.getKeySetter(); + } + + public ListenerList<V> getPreInsertListeners() { + return table.getPreInsertListeners(); + } + + public ListenerList<V> getPreUpdateListeners() { + return table.getPreUpdateListeners(); + } + + public ListenerList<V> getPreDeleteListeners() { + return table.getPreDeleteListeners(); + } + + public ListenerList<V> getPostInsertListeners() { + return table.getPostInsertListeners(); + } + + public ListenerList<V> getPostUpdateListeners() { + return table.getPostUpdateListeners(); + } + + public ListenerList<V> getPostDeleteListeners() { + return table.getPostDeleteListeners(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/extras/DbIterator.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,65 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.extras; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import fschmidt.db.DbTable; +import fschmidt.db.SQLRuntimeException; + + +public final class DbIterator implements Iterator { + private final DbTable table; + private final ResultSet rs; + private Object obj = null; + + public DbIterator(DbTable table,ResultSet rs) { + this.table = table; + this.rs = rs; + } + + public boolean hasNext() { + if( obj != null ) + return true; + try { + if( !rs.next() ) + return false; + obj = table.getDbObject(rs); + return true; + } catch(SQLException e) { + throw new SQLRuntimeException(e); + } + } + + public Object next() { + if( !hasNext() ) + throw new NoSuchElementException(); + return obj; + } + + public void remove() { + throw new UnsupportedOperationException(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/h2/DbDatabaseImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,55 @@ +/* +Copyright (c) 2009 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.h2; + +import fschmidt.db.DbArcana; + + +public final class DbDatabaseImpl extends fschmidt.db.base.DbDatabaseImpl implements DbArcana { + + public DbDatabaseImpl(String url,String user,String password) { + super(url,user,password); + try { + Class.forName("org.h2.Driver"); + } catch(ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public DbArcana arcana() { + return this; + } + + public String quoteIdentifier(String identifier) { + return "`" + identifier + "`"; + } + + public String getLastSeqValFn() { + return "IDENTITY()"; + } + + public String dateSub(String date,int n,String units) { + return "dateadd('" + units + "'," + (-n) + "," + date + ")"; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/mysql/DbDatabaseImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,72 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.mysql; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import fschmidt.db.DbArcana; + + +public final class DbDatabaseImpl extends fschmidt.db.base.DbDatabaseImpl implements DbArcana { + + public DbDatabaseImpl(String url,String user,String password) { + super(url,user,password); + try { + Class.forName("com.mysql.jdbc.Driver"); + } catch(ClassNotFoundException e) { + throw new RuntimeException(e); + } + setProperty("useUnicode","true"); + setProperty("characterEncoding","UTF-8"); + setProperty("jdbcCompliantTruncation","false"); + setProperty("cacheResultSetMetadata","true"); + setProperty("useCursorFetch","true"); + } + + public DbArcana arcana() { + return this; + } + + public String quoteIdentifier(String identifier) { + return "`" + identifier + "`"; + } + + public String getLastSeqValFn() { + return "last_insert_id()"; + } + + public String dateSub(String date,int n,String units) { + return "date_sub(" + date + ",interval " + n + " " + units + ")"; + } + + public void setValue(PreparedStatement stmt,int idx,Object value) + throws SQLException + { + if( value instanceof Boolean ) { + stmt.setString( idx, ((Boolean)value).booleanValue() ? "t" : "f" ); + } else { + super.setValue(stmt,idx,value); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/pool/DbDatabaseImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,134 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.pool; + +import fschmidt.db.DbDatabase; +import fschmidt.db.SQLRuntimeException; +import fschmidt.db.extend.DbDatabaseExt; +import fschmidt.db.extend.DbTransaction; +import fschmidt.db.extend.FilterDatabase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; + + +public class DbDatabaseImpl extends FilterDatabase { + private static final Logger logger = LoggerFactory.getLogger(DbDatabaseImpl.class); + + final Pool pool; + final String user; + + public DbDatabaseImpl(DbDatabase database,Pool pool,String user) { + super((DbDatabaseExt)database); + this.pool = pool; + this.user = user; + } + + public DbDatabaseImpl(DbDatabase database) { + this(database,new Pool(),null); + } + + DbDatabase database() { + return database; + } + + @Override public boolean isInTransaction() { + return pool.isInTransaction(); + } + + @Override public void beginTransaction() + throws SQLRuntimeException + { + if( isInTransaction() ) + throw new IllegalStateException("beginTransaction called in nested transaction"); + try { + Connection con = getConnection(); + con.setAutoCommit(false); + } catch(SQLException e) { + throw new SQLRuntimeException(e); + } + } + + @Override public void commitTransaction() { + pool.commitTransaction(); + } + + @Override public void endTransaction() { + pool.endTransaction(); + } + + @Override public void runBeforeCommit(Runnable r) { + if( isInTransaction() ) { + pool.runBeforeCommit(r); + } else { + super.runBeforeCommit(r); + } + } + + @Override public void runJustAfterCommit(Runnable r) { + if( isInTransaction() ) { + pool.runJustAfterCommit(r); + } else { + super.runJustAfterCommit(r); + } + } + + @Override public void runAfterCommit(Runnable r) { + if( isInTransaction() ) { + pool.runAfterCommit(r); + } else { + super.runAfterCommit(r); + } + } + + @Override public void ignoreRunnablesInThisTransaction() { + pool.ignoreRunnablesInThisTransaction(); + } + + @Override public DbTransaction getTransaction() { + return pool.getTransaction(); + } + + @Override public Connection getConnection() + throws SQLException + { + return pool.getConnection(this); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/pool/NestedConnection.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,139 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.pool; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.HashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import fschmidt.db.DbDatabase; +import fschmidt.db.DbObject; +import fschmidt.db.DbKey; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.SQLRuntimeException; +import fschmidt.db.extend.DbDatabaseExt; +import fschmidt.db.extend.DbTableExt; +import fschmidt.db.extend.DbTransaction; +import fschmidt.db.extend.DbRecordExt; +import fschmidt.db.extend.FilterDatabase; +import fschmidt.db.util.OverridingProxy; +import fschmidt.util.java.Stack; +import fschmidt.util.java.ArrayStack; + + +final class NestedConnection { + + private static class MyOverridingProxy extends OverridingProxy<Connection,NestedConnection> { + + MyOverridingProxy() { + super(Connection.class,NestedConnection.class); + } + + protected InvocationHandler newInvocationHandler(Connection obj,NestedConnection override) { + return new MyOverridingInvocationHandler(obj,override); + } + + protected class MyOverridingInvocationHandler extends OverridingInvocationHandler { + + MyOverridingInvocationHandler(Connection obj,NestedConnection override) { + super(obj,override); + } + + public Object invoke(Object proxy,Method method, Object[] args) + throws Throwable + { + if( !method.getName().equals("close") ) + override.setUser(); + return super.invoke(proxy,method,args); + } + + } + + } + + private static final MyOverridingProxy factory = new MyOverridingProxy(); + + private final PooledConnection pooledCon; + final Connection proxyCon; + final Exception initException = new Exception("connection constructed in "+Thread.currentThread()); + private boolean isClosed = false; + private final String user; + + NestedConnection(PooledConnection pooledCon,String user) { + this.user = user; + this.pooledCon = pooledCon; + proxyCon = factory.newInstance(pooledCon.con(),this); + } + + void setUser() throws SQLException { + pooledCon.setUser(user); + } + + protected void checkClosed() + throws SQLException + { + if( isClosed ) + throw new SQLException("connection is closed"); + } + + public void setAutoCommit(boolean autoCommit) + throws SQLException + { + checkClosed(); + pooledCon.setAutoCommit(autoCommit); + } + + public void commit() + throws SQLException + { + checkClosed(); + pooledCon.commit(); + } + + public void rollback() + throws SQLException + { + checkClosed(); + pooledCon.rollback(); + } + + public void close() + throws SQLException + { + checkClosed(); + isClosed = true; + pooledCon.close(this); + } + + public boolean isClosed() { + return isClosed; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/pool/Pool.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,176 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.pool; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.HashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import fschmidt.db.DbDatabase; +import fschmidt.db.DbObject; +import fschmidt.db.DbKey; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.SQLRuntimeException; +import fschmidt.db.extend.DbDatabaseExt; +import fschmidt.db.extend.DbTableExt; +import fschmidt.db.extend.DbTransaction; +import fschmidt.db.extend.DbRecordExt; +import fschmidt.db.extend.FilterDatabase; +import fschmidt.util.java.Stack; +import fschmidt.util.java.ArrayStack; + + +public final class Pool { + private static final Logger logger = LoggerFactory.getLogger(Pool.class); + + final Stack<PooledConnection> stack = new ArrayStack<PooledConnection>(); + private long idleTimeout = 0L; + private long idleExpires; + final ThreadLocal<PooledConnection> localCon = new ThreadLocal<PooledConnection>(); + + public long getIdleTimeout() { + return idleTimeout; + } + + public void setIdleTimeout(long idleTimeout) { + this.idleTimeout = idleTimeout; + } + + boolean isInTransaction() { + PooledConnection con = localCon.get(); + return con!=null && con.isInTransaction(); + } + + void commitTransaction() { + localCon.get().commitTransaction(); + } + + void endTransaction() { + PooledConnection con = localCon.get(); + if( con==null ) + throw new IllegalStateException("endTransaction called without beginTransaction"); + con.endTransaction(); + } + + void runBeforeCommit(Runnable r) { + PooledConnection con = localCon.get(); + if( !con.ignoreRunnablesInThisTransaction ) + con.beforeCommitList.add(r); + } + + void runJustAfterCommit(Runnable r) { + PooledConnection con = localCon.get(); + if( !con.ignoreRunnablesInThisTransaction ) + con.afterCommitList.add(0,r); + } + + void runAfterCommit(Runnable r) { + PooledConnection con = localCon.get(); + if( !con.ignoreRunnablesInThisTransaction ) + con.afterCommitList.add(r); + } + + void ignoreRunnablesInThisTransaction() { + if( !isInTransaction() ) + throw new IllegalStateException("not in transaction"); + localCon.get().ignoreRunnablesInThisTransaction = true; + } + + DbTransaction getTransaction() { + if( isInTransaction() ) { + return localCon.get().dbTrans; + } else { + return null; + } + } + + Connection getConnection(DbDatabaseImpl db) + throws SQLException + { + PooledConnection con = localCon.get(); + if( con==null || con.con().isClosed() ) { + con = getConnection2(db); + localCon.set(con); + pools.get().add(this); + } + return con.nest(db.user); + } + + private static final long timeout = 1000L*60*60; // 1 hour + + private synchronized PooledConnection getConnection2(DbDatabaseImpl db) + throws SQLException + { + long now = System.currentTimeMillis(); + while( !stack.isEmpty() ) { + PooledConnection con = stack.pop(); + Connection realCon = con.con(); + if( realCon.isClosed() ) + continue; + try { + if( now - con.lastUsed > timeout ) { + Statement stmt = realCon.createStatement(); + stmt.executeQuery("select 1"); + stmt.close(); + } + } catch(SQLException e) { + logger.info("corrupt connection dropped from pool"); + continue; + } + return con; + } + idleExpires = now + idleTimeout; + return new PooledConnection(db); + } + + boolean isExpired(long lastUsed) { + return idleTimeout > 0 && lastUsed > idleExpires; + } + + private static ThreadLocal<Set<Pool>> pools = new ThreadLocal<Set<Pool>>() { + protected Set<Pool> initialValue() { + return new HashSet<Pool>(); + } + }; + + public static void threadReset() { + Set<Pool> set = pools.get(); + for( Pool pool : set ) { + PooledConnection con = pool.localCon.get(); + if( con != null ) { + con.forceClose(); + } + } + set.clear(); + } + + public Connection getNativeConnection() { + return localCon.get().con(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/pool/PooledConnection.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,291 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.pool; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.ResultSet; +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.HashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import fschmidt.db.DbDatabase; +import fschmidt.db.DbObject; +import fschmidt.db.DbKey; +import fschmidt.db.DbObjectFactory; +import fschmidt.db.SQLRuntimeException; +import fschmidt.db.extend.DbDatabaseExt; +import fschmidt.db.extend.DbTableExt; +import fschmidt.db.extend.DbTransaction; +import fschmidt.db.extend.DbRecordExt; +import fschmidt.db.extend.FilterDatabase; +import fschmidt.util.java.Stack; +import fschmidt.util.java.ArrayStack; + + +public final class PooledConnection { + private static final Logger logger = LoggerFactory.getLogger(PooledConnection.class); + + private final Pool pool; + private Connection con; + private int trans = 0; + final List<Runnable> beforeCommitList = new ArrayList<Runnable>(); + final List<Runnable> afterCommitList = new ArrayList<Runnable>(); + DbTransaction dbTrans = null; + long lastUsed; + boolean ignoreRunnablesInThisTransaction = false; + private final Stack<NestedConnection> nesting = new ArrayStack<NestedConnection>(); + private volatile String user = null; + + PooledConnection(DbDatabaseImpl db) + throws SQLException + { + this.pool = db.pool; + this.con = db.database().getConnection(); + } + + protected void finalize() throws Throwable { + super.finalize(); + if( con == null ) + return; + if( !nesting.isEmpty() ) + logger.error("connection lost from pool: opened="+nesting.size()+" trans="+trans,nesting.peek().initException); + try { + this.con.close(); + } catch(SQLException e) { + } finally { + this.con = null; + } + } + + void setUser(String user) throws SQLException { + if( this.user == user ) { +/* + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("select CURRENT_USER"); + rs.next(); + String s = rs.getString("CURRENT_USER"); + rs.close(); + stmt.close(); + if( s.equals(user) ) + return; + logger.error("setUser error, user should be "+user+" but is "+s,new Exception()); +*/ + return; + } + this.user = null; + Statement stmt = con.createStatement(); + stmt.executeUpdate( + "set role " + user + ); + stmt.close(); + this.user = user; + } + + Connection nest(String user) { + NestedConnection nc = new NestedConnection(this,user); + nesting.push(nc); + return nc.proxyCon; + } + + private void transOver() + throws SQLException + { + con.setAutoCommit(true); + trans = 0; + dbTrans = null; + ignoreRunnablesInThisTransaction = false; + beforeCommitList.clear(); + afterCommitList.clear(); + } + + void setAutoCommit(boolean autoCommit) + throws SQLException + { + int opened = nesting.size(); + if( autoCommit==true ) { + //logger.warn("setAutoCommit true not well supported"); + if( trans > 0 ) { + if( trans != opened ) + throw new IllegalStateException("setAutoCommit to true with unclosed connections"); + transOver(); + return; + } + } else if( trans == 0 ) { + trans = opened; + dbTrans = new DbTransaction(); + } + con.setAutoCommit(autoCommit); + } + + void commit() + throws SQLException + { + int opened = nesting.size(); + if( trans != opened ) + throw new IllegalStateException("commit failed: trans="+trans+" opened="+opened); + final int opened2 = opened; + while( !beforeCommitList.isEmpty() ) { + Runnable[] a = beforeCommitList.toArray(new Runnable[0]); + beforeCommitList.clear(); + for( int i=0; i<a.length; i++ ) { + a[i].run(); + if( opened != opened2 ) { + logger.error("before commit opened="+opened+" opened2="+opened2+" runnable="+a[i]); + opened = opened2; + } + a[i] = null; + } + } + { // for now, to catch aborted transactions + Statement stmt = con.createStatement(); + stmt.executeQuery("select 1"); + stmt.close(); + } + con.commit(); + if( afterCommitList.isEmpty() ) { + dbTrans = new DbTransaction(); + } else { + Runnable[] a = afterCommitList.toArray(new Runnable[0]); + setAutoCommit(true); + for( int i=0; i<a.length; i++ ) { + a[i].run(); + if( opened != opened2 ) { + logger.error("after commit opened="+opened+" opened2="+opened2+" runnable="+a[i]); + opened = opened2; + } + a[i] = null; + } + setAutoCommit(false); + } + } + + void rollback() + throws SQLException + { + int opened = nesting.size(); + if( trans != opened ) + logger.warn("rollback called in nested transaction"); + con.rollback(); + beforeCommitList.clear(); + afterCommitList.clear(); + } + + void close(NestedConnection nestedCon) + throws SQLException + { + int i = nesting.indexOf(nestedCon); + if( trans==nesting.size() ) { + if( i != trans - 1 ) + logger.error("closing connection outside of transaction: i="+i+" trans="+trans,new Exception(nestedCon.initException)); + con.rollback(); // rollback everything since last commit + transOver(); + } + if( i < trans ) + logger.error("closing connection outside of transaction: i="+i+" trans="+trans,new Exception(nestedCon.initException)); + nesting.remove(nestedCon); + if( !nesting.isEmpty() ) { + nesting.get(nesting.size()-1).setUser(); // best guess + return; + } + if( trans != 0 ) + logger.error("trans = "+trans,new Exception()); + pool.localCon.remove(); + if( !beforeCommitList.isEmpty() ) { + logger.error("beforeCommitList = "+beforeCommitList,new Exception()); + beforeCommitList.clear(); + } + if( !afterCommitList.isEmpty() ) { + logger.error("afterCommitList = "+afterCommitList,new Exception()); + afterCommitList.clear(); + } + lastUsed = System.currentTimeMillis(); + synchronized(pool) { + if( pool.isExpired(lastUsed) ) { + con.close(); + con = null; + } else { + if( con.getAutoCommit() == false ) { + logger.error("autoCommit is false at close",new Exception()); + con.setAutoCommit(true); + } + pool.stack.push(this); + } + } + } + + Connection con() { + return con; + } + + boolean isInTransaction() { + return dbTrans!=null; + } + + void commitTransaction() { + if( !isInTransaction() ) + throw new IllegalStateException("commitTransaction called outside of transaction"); + try { + if( con.getAutoCommit()==true ) + throw new IllegalStateException("commitTransaction called outside of transaction"); + int opened = nesting.size(); + if( opened < trans ) + throw new IllegalStateException(); + if( opened > trans ) + throw new IllegalStateException("commitTransaction called with unclosed connections"); + commit(); + setAutoCommit(true); + } catch(SQLException e) { + throw new SQLRuntimeException(e); + } + } + + void endTransaction() { + try { + if( con.getAutoCommit()==false && nesting.size() > trans ) { + logger.error("endTransaction called with unclosed connections, closing them now",new Exception(nesting.peek().initException)); + while( nesting.size() > trans ) { + nesting.peek().close(); + } + } + nesting.peek().close(); + } catch(SQLException e) { + throw new SQLRuntimeException(e); + } + } + + void forceClose() { + logger.error("connection never closed: opened="+nesting.size()+" trans="+trans+" user="+user,nesting.peek().initException); + pool.localCon.remove(); + try { + con.close(); + } catch(SQLException e) { + logger.error("",e); + } + con = null; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/postgres/DbDatabaseImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,76 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.postgres; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Savepoint; +import java.sql.SQLException; +import fschmidt.db.DbArcana; + + +public final class DbDatabaseImpl extends fschmidt.db.base.DbDatabaseImpl implements DbArcana { + + public DbDatabaseImpl(String url,String user,String password) { + super(url,user,password); + try { + Class.forName("org.postgresql.Driver"); + } catch(ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public DbArcana arcana() { + return this; + } + + public String quoteIdentifier(String identifier) { + return "\"" + identifier + "\""; + } + + public String getLastSeqValFn() { + return "lastval()"; + } + + public String dateSub(String date,int n,String units) { + return "(" + date + " - interval '" + n + " " + units + "')"; + } + + public static int executeUpdateIgnoringDuplicateKeys(PreparedStatement stmt) + throws SQLException + { + PostgresExceptionHandler peh = new PostgresExceptionHandler(stmt); + try { + return stmt.executeUpdate(); + } catch(SQLException e) { + if( e.getMessage().startsWith("ERROR: duplicate key value violates unique constraint ") ) { + peh.handleException(); + return 0; + } + throw e; + } finally { + peh.close(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/postgres/PostgresExceptionHandler.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +package fschmidt.db.postgres; + +import java.sql.Connection; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.SQLException; + + +public final class PostgresExceptionHandler { + private boolean inTrans; + private Connection pgCon; + private Savepoint sp; + + public PostgresExceptionHandler(Statement stmt) throws SQLException { + pgCon = stmt.getConnection(); + inTrans = !pgCon.getAutoCommit(); + if( inTrans ) + sp = pgCon.setSavepoint(); + } + + public void handleException() throws SQLException { + if( inTrans ) + pgCon.rollback(sp); + } + + public void close() throws SQLException { + if( inTrans ) + pgCon.releaseSavepoint(sp); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/util/CacheMap.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,177 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.util; + +import java.lang.ref.ReferenceQueue; +import java.util.Map; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Set; +import java.util.AbstractSet; +import java.util.Iterator; + + +public abstract class CacheMap<K,V> extends AbstractMap<K,V> { + + protected static interface MyReference<K,V> { + public K key(); + public V get(); + public void clear(); + } + + protected abstract MyReference<K,V> newReference(K key,V value,ReferenceQueue<V> q); + + private final Map<K,MyReference<K,V>> cache = new HashMap<K,MyReference<K,V>>(); + private final ReferenceQueue<V> queue = new ReferenceQueue<V>(); + + private void sweep() { + while(true) { + @SuppressWarnings("unchecked") + MyReference<K,V> ref = (MyReference<K,V>)queue.poll(); + if( ref == null ) + return; + MyReference<K,V> mappedRef = cache.remove(ref.key()); + if( mappedRef != ref && mappedRef != null ) + cache.put( mappedRef.key(), mappedRef ); // put it back + } + } + + public int size() { + return cache.size(); + } + + public boolean isEmpty() { + return cache.isEmpty(); + } + + public boolean containsKey(Object key) { + return cache.containsKey(key); + } + + public V get(Object key) { + MyReference<K,V> ref = cache.get(key); + return ref==null ? null : ref.get(); + } + + public V put(K key,V value) { + sweep(); + MyReference<K,V> ref = cache.put( key, newReference(key,value,queue) ); + return ref==null ? null : ref.get(); + } + + public V remove(Object key) { + sweep(); + MyReference<K,V> ref = cache.remove(key); + return ref==null ? null : ref.get(); + } + + public void clear() { + sweep(); + cache.clear(); + } + +/* + public Object clone() { + GCCacheMap map = new GCCacheMap(); + map.cache = (HashMap)cache.clone(); + return map; + } +*/ + public Set<K> keySet() { + return cache.keySet(); + } + + public Set<Map.Entry<K,V>> entrySet() { + return new MySet(); + } + + + private class MySet extends AbstractSet<Map.Entry<K,V>> { + + public int size() { + return CacheMap.this.size(); + } + + public Iterator<Map.Entry<K,V>> iterator() { + return new MyIterator(cache.entrySet().iterator()); + } + + } + + private class MyIterator implements Iterator<Map.Entry<K,V>> { + Iterator<Map.Entry<K,MyReference<K,V>>> iter; + + MyIterator(Iterator<Map.Entry<K,MyReference<K,V>>> iter) { + this.iter = iter; + } + + public boolean hasNext() { + return iter.hasNext(); + } + + public void remove() { + iter.remove(); + } + + public Map.Entry<K,V> next() { + return new MyEntry( iter.next() ); + } + } + + private class MyEntry implements Map.Entry<K,V> { + Map.Entry<K,MyReference<K,V>> entry; + + MyEntry(Map.Entry<K,MyReference<K,V>> entry) { + this.entry = entry; + } + + public K getKey() { + return entry.getKey(); + } + + public V getValue() { + MyReference<K,V> ref = entry.getValue(); + return ref.get(); + } + + public V setValue(V value) { + MyReference<K,V> ref = entry.setValue( newReference(getKey(),value,queue) ); + return ref.get(); + } + + public boolean equals(Object o) { + if( o==null || !(o instanceof CacheMap.MyEntry) ) + return false; + MyEntry m = (MyEntry)o; + return entry.equals(m.entry); + } + + public int hashCode() { + K key = getKey(); + V value = getValue(); + return (key==null ? 0 : key.hashCode()) ^ + (value==null ? 0 : value.hashCode()); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/util/OverridingProxy.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,120 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.util; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.Modifier; +import java.util.Map; +import java.util.HashMap; +import java.util.Arrays; + + +public class OverridingProxy<C,S> { + private final Class<C> clsC; + private final Map<MethodDesc,Method> methods = new HashMap<MethodDesc,Method>(); + + public OverridingProxy(Class<C> clsC,Class<S> clsS) { + this.clsC = clsC; + for( Method m : clsS.getDeclaredMethods() ) { + if( Modifier.isPublic(m.getModifiers()) ) { + m.setAccessible(true); + methods.put( new MethodDesc(m), m ); + } + } + } + + public C newInstance(C obj,S override) { + @SuppressWarnings("unchecked") + C c = (C)Proxy.newProxyInstance( + clsC.getClassLoader(), + new Class[]{clsC}, + newInvocationHandler(obj,override) + ); + return c; + } + + protected InvocationHandler newInvocationHandler(C obj,S override) { + return new OverridingInvocationHandler(obj,override); + } + + protected class OverridingInvocationHandler implements InvocationHandler { + protected final C obj; + protected final S override; + + protected OverridingInvocationHandler(C obj,S override) { + this.obj = obj; + this.override = override; + } + + public Object invoke(Object proxy,Method method, Object[] args) + throws Throwable + { + Method m2 = methods.get(new MethodDesc(method)); + try { + return m2!=null ? m2.invoke(override,args) : method.invoke(obj,args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + } + + private static final class MethodDesc { + private final String name; + private final Class[] params; + private final Class rtn; + + MethodDesc(Method method) { + this.name = method.getName(); + this.params = method.getParameterTypes(); + this.rtn = method.getReturnType(); + } + + public boolean equals(Object obj) { + if( !(obj instanceof MethodDesc) ) + return false; + MethodDesc md = (MethodDesc)obj; + return name.equals(md.name) && Arrays.equals(params,md.params) && rtn.equals(md.rtn); + } + + public int hashCode() { + return name.hashCode(); + } + + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append( rtn ).append( " " ).append( name ).append( "(" ); + if( params.length > 0 ) { + buf.append( params[0] ); + for( int i=1; i<params.length; i++ ) { + buf.append( ',' ).append( params[i] ); + } + } + buf.append( ")" ); + return buf.toString(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/util/SoftCacheMap.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,57 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.util; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; + + +public final class SoftCacheMap<K,V> extends CacheMap<K,V> { + + static final class MySoftReference<K,V> extends SoftReference<V> implements MyReference<K,V> { + private final K key; + + MySoftReference(K key,V value,ReferenceQueue<V> q) { + super(value,q); + this.key = key; + } + + public K key() { + return key; + } + + public boolean equals(Object obj) { + Object o = this.get(); + if( o==null ) + return false; + SoftReference ref = (SoftReference)obj; + return o.equals(ref.get()); + } + + } + + protected MyReference<K,V> newReference(K key,V value,ReferenceQueue<V> q) { + return new MySoftReference<K,V>(key,value,q); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/util/Unique.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,56 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.util; + +import java.util.Map; +import java.util.HashMap; + + +public final class Unique<T> { + + private static class Val<T> { + T o; + int count; + } + + private Map<T,Val<T>> map = new HashMap<T,Val<T>>(); + + public synchronized T get(T o) { + Val<T> v = map.get(o); + if( v == null ) { + v = new Val<T>(); + v.o = o; + map.put(o,v); + } + v.count++; + return v.o; + } + + public synchronized void free(T o) { + Val<T> v = map.get(o); + if (v==null) return; + if( --v.count == 0 ) + map.remove(o); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/db/util/WeakCacheMap.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,57 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.db.util; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; + + +public class WeakCacheMap<K,V> extends CacheMap<K,V> { + + static final class MyWeakReference<K,V> extends WeakReference<V> implements MyReference<K,V> { + private final K key; + + MyWeakReference(K key,V value,ReferenceQueue<V> q) { + super(value,q); + this.key = key; + } + + public K key() { + return key; + } + + public boolean equals(Object obj) { + Object o = this.get(); + if( o==null ) + return false; + WeakReference ref = (WeakReference)obj; + return o.equals(ref.get()); + } + + } + + protected MyReference<K,V> newReference(K key,V value,ReferenceQueue<V> q) { + return new MyWeakReference<K,V>(key,value,q); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/Html.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,253 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.html; + +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Arrays; +import java.util.Set; +import java.util.HashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import fschmidt.util.java.HtmlUtils; + + +public final class Html extends ArrayList<Object> { + private static final Logger logger = LoggerFactory.getLogger(Html.class); + + public static final String TEXTAREA = "textarea"; + public static final String SCRIPT = "script"; + public static final String STYLE = "style"; + + private int startingLine = 0; + private boolean removeBadTags = false; + private Set<String> containerTags = new HashSet<String>(Arrays.asList(SCRIPT,STYLE)); + + public Html() {} + + public Html(String text) { + parse(text); + } + + public Set<String> containerTags() { + return containerTags; + } + + public void setStartingLine(int startingLine) { + this.startingLine = startingLine; + } + + public void removeBadTags(boolean removeBadTags) { + this.removeBadTags = removeBadTags; + } + + public void parse(String text) { + int len = text.length(); + int i = 0; + int i2Prev = 0; + int line = startingLine; +outer: + while( i < len ) { + int i2 = text.indexOf('<',i); + while( i2 != -1 && i2+1 < len ) { + char c = text.charAt(i2+1); + if( Character.isLetter(c) || c=='/' || c=='!' ) + break; + i2 = text.indexOf('<',i2+1); + } + if( i2 == -1 ) { + add( text.substring(i) ); + break; + } + if( i < i2 ) + add( text.substring(i,i2) ); + if( text.startsWith("<!--",i2) ) { + i = text.indexOf("-->",i2+4); + if( i == -1 ) { + add( text.substring(i2) ); + break; + } + add( new HtmlComment( text.substring(i2+4,i) ) ); + i += 3; + } else if( text.startsWith("<![CDATA[",i2) ) { + i = text.indexOf("]]>",i2+9); + if( i == -1 ) { + add( text.substring(i2) ); + break; + } + add( new HtmlCdata( text.substring(i2+9,i) ) ); + i += 3; + } else { + i = text.indexOf('>',i2); + if( i == -1 ) { + add( text.substring(i2) ); + break; + } + line += lines(text,i2Prev,i2); + i2Prev = i2; + String tagText = text.substring(i2+1,i); + try { + HtmlTag tag = new HtmlTag(tagText); + tag.lineNumber = line; + String tagName = tag.getName().toLowerCase(); + if( containerTags.contains(tagName) ) { + i2 = i; + String endTagName = '/' + tagName; + while(true) { + i2 = text.indexOf('<',i2+1); + if( i2 == -1 ) + break; + int i3 = text.indexOf('>',i2); + if( i3 == -1 ) + break; + int j = i2+1; + while( j<i3 && !Character.isWhitespace(text.charAt(j)) ) j++; + String s = text.substring(i2+1,j); + if( s.equalsIgnoreCase(endTagName) ) { + HtmlTag tag2 = new HtmlTag( text.substring(i2+1,i3) ); + line += lines(text,i2Prev,i2); + tag2.lineNumber = line; + i2Prev = i2; + String text2 = text.substring(i+1,i2); + HtmlTextContainer textContainer = + tagName.equals(TEXTAREA) ? + new HtmlTextarea(tag,text2,tag2) + : tagName.equals(SCRIPT) ? + new HtmlScript(tag,text2,tag2) + : tagName.equals(STYLE) ? + new HtmlStyle(tag,text2,tag2) + : + new HtmlTextContainer(tag,text2,tag2) + ; + add( textContainer ); + i = i3 + 1; + continue outer; + } + } + logger.warn("unclosed "+tagName); + } + i += 1; + add( tag ); + } catch(HtmlTag.BadTag e) { +// logger.debug("bad tag",e); + i += 1; + if( !removeBadTags ) { + add( "<" ); + add( HtmlUtils.htmlEncode(tagText) ); + add( ">" ); + } + } + } + } + } + + @Override public String toString() { + StringBuilder buf = new StringBuilder(); + for( Object o : this ) { + buf.append( o ); + } + return buf.toString(); + } + + private static int lines(String text,int start,int end) { + int n = 0; + int i = start - 1; + while(true) { + i = text.indexOf('\n',i+1); + if( i == -1 || i >= end ) + return n; + n++; + } + } + + public Html flatten() { + Html html = new Html(); + flattenTo(html); + return html; + } + + void flattenTo(Html html) { + for( Object obj : this ) { + if( obj instanceof HtmlNode ) { + ((HtmlNode)obj).flattenTo(html); + } else { + html.add(obj); + } + } + } + + public Html deepen() { + Iterator iter = iterator(); + Html html = deepen(iter); + if( iter.hasNext() ) + throw new RuntimeException("unmatched end tag:\n"+html); + return html; + } + + private static Html deepen(Iterator iter) { + Html html = new Html(); + while( iter.hasNext() ) { + Object obj = iter.next(); + if( obj instanceof HtmlTag && !(obj instanceof HtmlNode) ) { + HtmlTag tag = (HtmlTag)obj; + if( !tag.isEmpty() ) { + String name = tag.getName(); + if( name.startsWith("/") ) { + html.add(tag); + return html; + } + Html children = deepen(iter); + HtmlTag endTag = (HtmlTag)children.get(children.size()-1); + if( endTag.getName().equals("/"+name) ) { + children.remove(children.size()-1); + html.add( new HtmlNode(tag,children) ); + continue; + } else { + html.add(tag); + html.addAll(children); + return html; + } + } + } + html.add(obj); + } + return html; + } + + public static void main(String[] args) throws Exception { +/* + String page = fschmidt.util.java.IoUtils.readPage("http://www.yahoo.com/"); + Html html = new Html(page); + String s = html.toString(); + System.out.print(s); +// System.out.println(html.size()); +*/ + String page = fschmidt.util.java.IoUtils.readAll(new InputStreamReader(System.in)); + Html html = new Html(page); + for( Iterator i=html.iterator(); i.hasNext(); ) { + Object o = i.next(); + System.out.println(o.getClass().getName()+" - "+o); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/HtmlCdata.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,28 @@ +package fschmidt.html; + + +public final class HtmlCdata { + public final String text; + + public HtmlCdata(String text) { + this.text = text; + } + + public String toString() { + return "<![CDATA["+text+"]]>"; + } + + public boolean equals(Object obj) { + if( obj == this ) + return true; + if( !(obj instanceof HtmlCdata) ) + return false; + HtmlCdata m = (HtmlCdata)obj; + return m.text.equals(text); + } + + public int hashCode() { + return text.hashCode(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/HtmlComment.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,50 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.html; + + +public final class HtmlComment { + public final String text; + + public HtmlComment(String text) { + this.text = text; + } + + public String toString() { + return "<!--"+text+"-->"; + } + + public boolean equals(Object obj) { + if( obj == this ) + return true; + if( !(obj instanceof HtmlComment) ) + return false; + HtmlComment m = (HtmlComment)obj; + return m.text.equals(text); + } + + public int hashCode() { + return text.hashCode(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/HtmlNode.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,32 @@ +package fschmidt.html; + + +public final class HtmlNode extends HtmlTag { + public final Html children; + + public HtmlNode(HtmlTag tag,Html children) { + super(tag); + if( tag.isEmpty() ) + throw new RuntimeException(); + this.children = children; + } + + @Override public String toString() { + return super.toString() + children + "</" + getName() + ">"; + } + + @Override public boolean equals(Object obj) { + if( !super.equals(obj) ) + return false; + if( !(obj instanceof HtmlNode) ) + return false; + HtmlNode node = (HtmlNode)obj; + return children.equals(node.children); + } + + void flattenTo(Html html) { + html.add( new HtmlTag(this) ); + children.flattenTo(html); + html.add( new HtmlTag( "/" + getName() ) ); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/HtmlScript.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,144 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.html; + +import java.util.List; +import java.util.ArrayList; + + +public final class HtmlScript extends HtmlTextContainer { + + public HtmlScript(HtmlTag startTag,String text,HtmlTag endTag) { + super(startTag,text,endTag); + } + + + // odd strings are string constants + public static String[] split(String text) { + List<String> list = new ArrayList<String>(); + char[] a = text.toCharArray(); + int len = a.length; + StringBuilder buf = new StringBuilder(); +outer: + for( int i=0; i<len; i++ ) { + char c = a[i]; + switch(c) { + case '"': + case '\'': + StringBuilder bufS = new StringBuilder(); + bufS.append(c); + for( int j=i+1; j<len && j!='\n'; j++ ) { + char c2 = a[j]; + bufS.append(c2); + if( c2=='\\' ) { + bufS.append(a[++j]); + continue; + } + if( c2==c ) { + list.add(buf.toString()); + list.add(bufS.toString()); + i = j; + buf.setLength(0); + continue outer; + } + } + default: + buf.append(c); + } + } + list.add(buf.toString()); + return (String[])list.toArray(new String[0]); + } + + public static String unquote(String s) { + char c = s.charAt(0); + if( c!='"' && c!='\'' || c!=s.charAt(s.length()-1) ) + throw new RuntimeException(); + s = s.substring(1,s.length()-1); + StringBuilder buf = new StringBuilder(); + int i = 0; + while(true) { + int i2 = s.indexOf('\\',i); + if( i2 == -1 ) + break; + buf.append(s.substring(i,i2)); + c = s.charAt(i2+1); + switch(c) { + case 'b': + buf.append('\b'); + break; + case 'f': + buf.append('\f'); + break; + case 'n': + buf.append('\n'); + break; + case 'r': + buf.append('\r'); + break; + case 't': + buf.append('\t'); + break; + default: + buf.append(c); + } + i = i2 + 2; + } + buf.append(s.substring(i)); + return buf.toString(); + } + + public static String quote(String s,char quote) { + StringBuilder buf = new StringBuilder(); + char[] a = s.toCharArray(); + buf.append(quote); + for( int i=0; i<a.length; i++ ) { + char c = a[i]; + switch(c) { + case '\b': + buf.append("\\b"); + break; + case '\f': + buf.append("\\f"); + break; + case '\n': + buf.append("\\n"); + break; + case '\r': + buf.append("\\r"); + break; + case '\t': + buf.append("\\t"); + break; + case '\\': + case '"': + case '\'': + buf.append('\\'); + default: + buf.append(c); + } + } + buf.append(quote); + return buf.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/HtmlStyle.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.html; + + +public final class HtmlStyle extends HtmlTextContainer { + + public HtmlStyle(HtmlTag startTag,String text,HtmlTag endTag) { + super(startTag,text,endTag); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/HtmlTag.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,335 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + + +package fschmidt.html; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Iterator; +import java.util.Collection; +import java.util.Collections; + + +public class HtmlTag { + + static final class BadTag extends RuntimeException { + private BadTag(String msg) { + super(msg); + } + } + + public static final class Attribute { + private final String spaceBeforeName; + private final String name; + private final String spaceAfterName; + private final String spaceBeforeValue; + private final String value; + + protected Attribute(String spaceBeforeName,String name,String spaceAfterName,String spaceBeforeValue,String value) { + this.spaceBeforeName = spaceBeforeName; + this.name = name; + this.spaceAfterName = spaceAfterName; + this.spaceBeforeValue = spaceBeforeValue; + this.value = value; + } + + public String getSpaceBeforeName() { + return spaceBeforeName; + } + + public String getName() { + return name; + } + + public String getSpaceAfterName() { + return spaceAfterName; + } + + public String getSpaceBeforeValue() { + return spaceBeforeValue; + } + + public String getValue() { + return value; + } + + @Override public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(name); + if( value != null ) { + buf.append(spaceAfterName); + buf.append('='); + buf.append(spaceBeforeValue); + buf.append(value); + } + return buf.toString(); + } + } + + private String name; + private final Map<String,Attribute> attrMap; + private boolean isEmpty; + int lineNumber = -1; + private final String spaceAtEnd; + + private static int toEndName(String text,int i,int len) { + if( i==len ) + return i; + char c = text.charAt(i); + switch(c) { + case '"': + case '\'': + i = text.indexOf(c,i+1); + return i==-1 ? len : i+1; + default: + if( Character.isWhitespace(c) ) { + throw new RuntimeException("text="+text+" i="+i); + } + do { + i++; + } while( i<len && (c=text.charAt(i))!='=' && !Character.isWhitespace(c) ); + return i; + } + } + + private static int toEndValue(String text,int i,int len) { + if( i==len ) + return i; + char c = text.charAt(i); + switch(c) { + case '"': + case '\'': + i = text.indexOf(c,i+1); + return i==-1 ? len : i+1; + default: + if( Character.isWhitespace(c) ) { + throw new RuntimeException("text="+text+" i="+i); + } + do { + i++; + } while( i<len && !Character.isWhitespace(text.charAt(i)) ); + return i; + } + } + + public HtmlTag(String text) throws BadTag { + attrMap = new LinkedHashMap<String,Attribute>(); + if( text.endsWith("/") ) { + text = text.substring(0,text.length()-1); + isEmpty = true; + } else { + isEmpty = false; + } + int len = text.length(); + int i = 0; + int i2 = i; + if( i2<len && text.charAt(i2)=='/' ) + i2++; + while( i2<len ) { + char c = text.charAt(i2); + if( Character.isWhitespace(c) ) + break; + if( !( Character.isLetterOrDigit(c) || c=='_' || c=='.' || c=='-' || c==':' ) ) + throw new BadTag("invalid tag name for <"+text+">"); + i2++; + } + name = text.substring(i,i2); + i = i2; + while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; + while( i<len ) { + String attrSpaceBeforeName = text.substring(i2,i); + i2 = toEndName(text,i,len); + String attrName = text.substring(i,i2); + i = i2; + while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; + String attrSpaceAfterName = ""; + String attrSpaceBeforeValue = ""; + String attrValue = null; + if( i<len && text.charAt(i) == '=' ) { + attrSpaceAfterName = text.substring(i2,i); + i++; + i2 = i; + while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; + attrSpaceBeforeValue = text.substring(i2,i); + i2 = toEndValue(text,i,len); + attrValue = text.substring(i,i2); + if( attrValue.indexOf('<') != -1 || attrValue.indexOf('>') != -1 ) + throw new BadTag("invalid attribute value: "+attrValue); + i = i2; + while( i<len && Character.isWhitespace(text.charAt(i)) ) i++; + } + if( setAttribute(attrSpaceBeforeName,attrName,attrSpaceAfterName,attrSpaceBeforeValue,attrValue) != null ) + throw new BadTag("duplicate attribute: "+attrName); + } + spaceAtEnd = text.substring(i2,i); + } + + public HtmlTag(HtmlTag tag) { + this.name = tag.name; + this.attrMap = new LinkedHashMap<String,Attribute>(tag.attrMap); + this.isEmpty = tag.isEmpty; + this.spaceAtEnd = tag.spaceAtEnd; + } + + public HtmlTag cloneTag() { + HtmlTag tag = new HtmlTag(this); + tag.lineNumber = lineNumber; + return tag; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isEmpty() { + return isEmpty; + } + + public void setEmpty(boolean isEmpty) { + this.isEmpty = isEmpty; + } + + public String getAttributeName(String name) { + String key = unquote(name).toLowerCase(); + Attribute attr = attrMap.get(key); + return attr==null ? null : attr.name; + } + + public String getAttributeValue(String name) { + String key = unquote(name).toLowerCase(); + Attribute attr = attrMap.get(key); + return attr==null ? null : attr.value; + } + + public boolean hasAttribute(String name) { + String key = unquote(name).toLowerCase(); + return attrMap.containsKey(key); + } + + public void setAttribute(String name,String value) { + setAttribute(" ",name,"","",value); + } + + protected Attribute setAttribute(String spaceBeforeName,String name,String spaceAfterName,String spaceBeforeValue,String value) { + String key = unquote(name).toLowerCase(); + Attribute attr = new Attribute(spaceBeforeName,name,spaceAfterName,spaceBeforeValue,value); + return attrMap.put(key,attr); + } + + public void removeAttribute(String name) { + String key = unquote(name).toLowerCase(); + attrMap.remove(key); + } + + public String[] getAttributeNames() { + String[] a = new String[attrMap.size()]; + int i = 0; + for( Attribute attr : attrMap.values() ) { + a[i++] = attr.name; + } + return a; + } + + @Override public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append('<'); + buf.append(name); + for( Attribute attr : attrMap.values() ) { + buf.append(attr.spaceBeforeName); + buf.append(attr); + } + buf.append(spaceAtEnd); + if(isEmpty) + buf.append('/'); + buf.append('>'); + return buf.toString(); + } + + public static String unquote(String s) { + if( s==null || s.length()<=1 ) + return s; + char c = s.charAt(0); + return (c=='"' || c=='\'') && s.charAt(s.length()-1)==c + ? s.substring(1,s.length()-1) : s; + } + + public static String quote(String s) { + if( s==null ) + return null; + StringBuilder buf = new StringBuilder(); + buf.append('"'); + int i = 0; + while(true) { + int i2 = s.indexOf('"',i); + if( i2 == -1 ) { + buf.append(s.substring(i)); + break; + } else { + buf.append(s.substring(i,i2)); + buf.append("""); + i = i2 + 1; + } + } + buf.append('"'); + return buf.toString(); + } + + @Override public boolean equals(Object obj) { + if( obj == this ) + return true; + if( !(obj instanceof HtmlTag) ) + return false; + HtmlTag tag = (HtmlTag)obj; + return getName().equalsIgnoreCase(tag.getName()) + && isEmpty() == tag.isEmpty() + && getAttributeMap().equals(tag.getAttributeMap()) + ; + } + + public Map<String,String> getAttributeMap() { + Map<String,String> map = new HashMap<String,String>(); + for( Attribute attr : attrMap.values() ) { + map.put(unquote(attr.name),unquote(attr.value)); + } + return map; + } + + public Collection<Attribute> getAttributes() { + return Collections.unmodifiableCollection(attrMap.values()); + } + + public int getLineNumber() { + return lineNumber; + } + + public String getSpaceAtEnd() { + return spaceAtEnd; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/HtmlTextContainer.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,54 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.html; + + +public class HtmlTextContainer { + public final HtmlTag startTag; + public String text; + public final HtmlTag endTag; + + protected HtmlTextContainer(HtmlTag startTag,String text,HtmlTag endTag) { + this.startTag = startTag; + this.text = text; + this.endTag = endTag; + } + + @Override public String toString() { + return startTag.toString() + text + endTag.toString(); + } + + @Override public boolean equals(Object obj) { + if( obj == this ) + return true; + if( !(obj instanceof HtmlTextContainer) ) + return false; + HtmlTextContainer m = (HtmlTextContainer)obj; + return m.startTag.equals(startTag) && m.text.equals(text) && m.endTag.equals(endTag); + } + + @Override public int hashCode() { + return startTag.hashCode() + text.hashCode(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/html/HtmlTextarea.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.html; + + +public final class HtmlTextarea extends HtmlTextContainer { + + public HtmlTextarea(HtmlTag startTag,String text,HtmlTag endTag) { + super(startTag,text,endTag); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/license.txt Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,88 @@ +GNU GENERAL PUBLIC LICENSE +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + +a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + +c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + +END OF TERMS AND CONDITIONS
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/CrawlSite.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,129 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools; + +import java.io.IOException; +import java.net.URL; +import java.util.Set; +import java.util.HashSet; +import java.util.Map; +import java.util.HashMap; +import java.util.Arrays; +import java.util.Iterator; +import fschmidt.util.java.IoUtils; +import fschmidt.html.Html; +import fschmidt.html.HtmlTag; + + +public class CrawlSite { + private static final Set<String> endings = new HashSet<String>( Arrays.asList( new String[]{ + "html", "htm", "ssp", "jsp", "jtp", + } ) ); + private static final Map<String,String> tagMap = new HashMap<String,String>(); + static { + tagMap.put("a","href"); + tagMap.put("area","href"); + tagMap.put("base","href"); + tagMap.put("form","action"); + tagMap.put("frame","src"); + tagMap.put("img","src"); + tagMap.put("link","href"); + tagMap.put("input","src"); + } + + private String baseUrl; + private Set<String> done = new HashSet<String>(); + + public CrawlSite(String baseUrl) { + this.baseUrl = baseUrl; + } + + protected boolean isHtml(String url) { + url = url.substring( url.lastIndexOf('/') + 1 ); + int i = url.indexOf('.'); + if( i == -1 ) + return true; + String ending = url.substring(i+1); + return endings.contains(ending); + } + + protected void process(String url) { + System.out.println(url); + } + + public void crawl() + throws IOException + { + crawl(baseUrl); + } + + private void crawl(String url) + throws IOException + { + if( !done.add(url) ) + return; + + process(url); + + if( !isHtml(url) ) + return; + + String page = IoUtils.readPage(url); + Html html = new Html(page); + Iterator iter = html.iterator(); + while( iter.hasNext() ) { + Object o = iter.next(); + if( !(o instanceof HtmlTag) ) + continue; + HtmlTag tag = (HtmlTag)o; + String tagName = tag.getName().toLowerCase(); + String attrName = (String)tagMap.get(tagName); + if( attrName==null ) + continue; + String val = tag.getAttributeValue(attrName); + if( val==null ) + continue; + String url2 = HtmlTag.unquote(val); + url2 = new URL(new URL(url),url2).toString(); + if( !url2.startsWith(baseUrl) ) + continue; + int i = url2.indexOf('#'); + if( i != -1 ) + url2 = url2.substring(0,i); + String file = url2.substring( url2.lastIndexOf('/') + 1 ); + if( file.length() > 0 && file.indexOf('.') == -1 ) + url2 += '/'; + try { + crawl(url2); + } catch(IOException e) { +// System.err.println(e); + System.err.println(e+" referred to from "+url); +// e.printStackTrace(); + } + } + } + + public static void main(String[] args) throws Exception { + new CrawlSite(args[0]).crawl(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/Jmp.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,120 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools; + +import fschmidt.util.java.IoUtils; + +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; + + +public final class Jmp { + + public static void main(String args[]) throws Exception { + for( int iFile=0; iFile<args.length; iFile++ ) { + String inFile = args[iFile]; + if( !inFile.endsWith(".jmp") ) + throw new RuntimeException(inFile+" doesn't end with '.jmp'"); + String outFile = inFile.substring(0,inFile.length()-4) + ".java"; + PrintWriter out = new PrintWriter( IoUtils.newFileWriter(outFile) ); + int tabs = 0; + String jsp = IoUtils.read( new File(inFile) ); + int i = 0; + boolean plus = false; + while(true) { + int i1 = jsp.indexOf("<%",i); + if( i1 == -1 ) { + out.close(); + break; + } + int i2 = jsp.indexOf("%>",i1); + if( i2 == -1 ) { + System.err.println("'<%' not matched with '%>'"); + System.exit(-1); + } + if( i1 > i ) { + out.print("\t\t"); + if( plus ) + out.print("+"); + out.print("\""); + for( ; i<i1; i++ ) { + char c = jsp.charAt(i); + switch(c) { + case '\r': + out.print("\\r"); + break; + case '\n': + out.print("\\n"); + int j; + for( j=0; j<tabs && i+j+1<i1 && jsp.charAt(i+j+1)=='\t'; j++ ); + i += j; + break; + case '\\': + case '"': + out.print('\\'); + default: + out.print(c); + } + } + out.println("\""); + } + i += 2; + char c = jsp.charAt(i); + if( c == '=' ) { + out.print("\t\t+("); + out.print( jsp.substring(i+1,i2) ); + out.println(")"); + plus = true; + } else if( c == '+' ) { + out.print("\t\t+(lang."); + String methodToCall = jsp.substring(i+1,i2); + out.print(methodToCall); + if (!methodToCall.endsWith(")")) + out.print("()"); + out.println(")"); + plus = true; + } else { + out.println( jsp.substring(i,i2) ); + plus = false; + int tabs2 = 0; + loop: + for( int j = i2 - 1; true; j-- ) { + switch( jsp.charAt(j) ) { + case '\t': + tabs2++; + break; + case '\n': + tabs = tabs2; + break loop; + default: + break loop; + } + } + } + i = i2 + 2; + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/Jtp.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,134 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools; + +import java.io.PrintWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.CharArrayWriter; +import fschmidt.util.java.IoUtils; + + +public final class Jtp { + public static class JtpException extends Exception { + JtpException(String msg) { + super(msg); + } + } + + public static void main(String args[]) throws Exception { + try { + for( int iFile=0; iFile<args.length; iFile++ ) { + String inFile = args[iFile]; + if( !inFile.endsWith(".jtp") ) + throw new RuntimeException(inFile+" doesn't end with '.jtp'"); + String outFile = inFile.substring(0,inFile.length()-4) + ".java"; + String jtp = IoUtils.read( new File(inFile) ); + IoUtils.write( new File(outFile), toJava(jtp) ); + } + } catch(JtpException e) { + System.err.println(e.getMessage()); + System.exit(-1); + } + } + + public static String toJava(String jtp) + throws JtpException + { + if( jtp==null ) + throw new JtpException("null argument"); + CharArrayWriter out0 = new CharArrayWriter(); + PrintWriter out = new PrintWriter(out0); + int tabs = 0; + int i = 0; + while(true) { + int i1 = jtp.indexOf("<%",i); + boolean done = i1 == -1; + if( i1 > i || done && jtp.substring(i).trim().length() > 0 ) { + if( done ) + i1 = jtp.length(); + out.print("\t\tout.print( \""); + for( ; i<i1; i++ ) { + char c = jtp.charAt(i); + switch(c) { + case '\r': + out.print("\\r"); + break; + case '\n': + out.print("\\n"); + int j; + for( j=0; j<tabs && i+j+1<i1 && jtp.charAt(i+j+1)=='\t'; j++ ); + i += j; + break; + case '\\': + case '"': + out.print('\\'); + default: + out.print(c); + } + } + out.println("\" );"); + } + if( done ) { + out.flush(); + return out0.toString(); + } + int i2 = jtp.indexOf("%>",i1); + if( i2 == -1 ) { + throw new JtpException("'<%' not matched with '%>'"); + } + i += 2; + char c = jtp.charAt(i); + if( c == '=' ) { + out.print("\t\tout.print( ("); + out.print( jtp.substring(i+1,i2) ); + out.println(") );"); + } else if( c == '+' ) { + out.print("\t\tout.print( (lang."); + String methodToCall = jtp.substring(i+1,i2); + out.print(methodToCall); + if (!methodToCall.endsWith(")")) + out.print("()"); + out.println(") );"); + } else { + out.println( jtp.substring(i,i2) ); + int tabs2 = 0; + loop: + for( int j = i2 - 1; true; j-- ) { + switch( jtp.charAt(j) ) { + case '\t': + tabs2++; + break; + case '\n': + tabs = tabs2; + break loop; + default: + break loop; + } + } + } + i = i2 + 2; + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/Mmake.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,309 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.File; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.util.Set; +import java.util.HashSet; +import java.util.List; +import java.util.ArrayList; +import java.util.Date; +import java.util.regex.Pattern; + + +/** + * Doing "java fschmidt.tools.Mmake" on the command line will build + * make files in the current directory and all sub-directories. + * + * @author frank + */ +public final class Mmake { + private static String compiler = "javac -g -encoding UTF8"; + private static String toolsPath; + private static String[] classpath = System.getProperty("java.class.path").split(Pattern.quote(File.pathSeparator),-1); + + static String findPathTo(String file) { + for( String s : classpath ) { + File path = new File(s); + if( path.isAbsolute() && new File(path,file).exists() ) + return path.getPath() + File.separator; + } + return null; + } + + public static void main(String args[]) throws Exception { + for( int i=0; i<args.length; i++ ) { + compiler += " " + args[i]; + } + String subpath = "fschmidt"+File.separator+"tools"; + String path = findPathTo(subpath+File.separator+"Mmake.java"); + if( path != null ) { + toolsPath = (path+subpath).replace('\\','/'); + } + mmake("."); + } + + private static class R { + boolean java = false; + boolean j_p = false; + boolean rmi = false; + boolean jjt = false; + } + + static R mmake(String dir) + throws IOException + { + R rtn = new R(); + PrintWriter out = null; + File dirF = new File(dir); + String[] ls = dirF.list(); + if( ls == null ) + return rtn; + Set<String> java = new HashSet<String>(); + Set<String> jtp = new HashSet<String>(); + Set<String> jmp = new HashSet<String>(); + Set<String> rmi = new HashSet<String>(); + Set<String> jjt = new HashSet<String>(); + List<String> dirs = new ArrayList<String>(); + List<String> dirs2 = new ArrayList<String>(); + List<String> dirsRmi = new ArrayList<String>(); + List<String> dirsJjt = new ArrayList<String>(); + for( int i=0; i<ls.length; i++ ) { + String file = ls[i]; + if( file.endsWith(".java") ) { + String s = file.substring(0,file.length()-5); + java.add(s); + } + if( file.endsWith(".jtp") ) { + String s = file.substring(0,file.length()-4); + java.add(s); + jtp.add(s); + } + if( file.endsWith(".jmp") ) { + String s = file.substring(0,file.length()-4); + java.add(s); + jmp.add(s); + } + if( file.endsWith(".rmi") ) { + String s = file.substring(0,file.length()-4); + rmi.add(s); + } + if( file.endsWith(".jjt") ) { + String s = file.substring(0,file.length()-4); + jjt.add(s); + } + String d = dir + "/" + file; + if( new File(d).isDirectory() ) { + R r = mmake(d); + if( r.java ) { + dirs.add(file); + if( r.j_p ) + dirs2.add(file); + if( r.rmi ) + dirsRmi.add(file); + if( r.jjt ) + dirsJjt.add(file); + } + } + } + if( java.isEmpty() && dirs.isEmpty() ) + return rtn; + rtn.java = true; + String[] a; + out = init(dir); + if( !java.isEmpty() ) { + a = (String[])java.toArray(new String[0]); + for( int i=0; i<a.length; i++ ) { + out.println("\\"); + out.print("\t\t"+a[i]+".class "); + } + } + out.println(); + String[] subs = (String[])dirs.toArray(new String[0]); + int n = subs.length; + for( int i=0; i<n; i++ ) + out.println("\tcd `pwd`/"+subs[i]+"; make _src"); + + rtn.j_p = !jtp.isEmpty() || !jmp.isEmpty() || !dirs2.isEmpty(); + out.println(); + if( !rtn.j_p ) { + out.println("_java:"); + } else { + if( toolsPath==null ) { + out.print("_java:\t"); + } else { + boolean hasJtp = !jtp.isEmpty(); + boolean hasJmp = !jmp.isEmpty(); + if( hasJtp ) { + out.println(toolsPath+"/Jtp.class: "+toolsPath+"/Jtp.java"); + out.println("\tjavac "+toolsPath+"/Jtp.java"); + out.println(); + } + if( hasJmp ) { + out.println(toolsPath+"/Jmp.class: "+toolsPath+"/Jmp.java"); + out.println("\tjavac "+toolsPath+"/Jmp.java"); + out.println(); + } + out.print("_java:\t"); + if( hasJtp ) + out.print(toolsPath+"/Jtp.class "); + if( hasJmp ) + out.print(toolsPath+"/Jmp.class "); + } + a = (String[])jtp.toArray(new String[0]); + for( int i=0; i<a.length; i++ ) { + out.println("\\"); + out.print("\t\t"+a[i]+".java "); + } + a = (String[])jmp.toArray(new String[0]); + for( int i=0; i<a.length; i++ ) { + out.println("\\"); + out.print("\t\t"+a[i]+".java "); + } + out.println(); + String[] subs2 = (String[])dirs2.toArray(new String[0]); + int n2 = subs2.length; + for( int i=0; i<n2; i++ ) + out.println("\tcd `pwd`/"+subs2[i]+"; make _java"); + } + + rtn.rmi = !rmi.isEmpty() || !dirsRmi.isEmpty(); + out.println(); + out.print("_rmi: "); + if( !rtn.rmi ) { + out.println(); + } else { + a = (String[])rmi.toArray(new String[0]); + for( int i=0; i<a.length; i++ ) { + String s = a[i]; + out.println("\\"); + out.print("\t\t"+s+"_Stub.class "); + } + out.println(); + String absPath = dirF.getCanonicalPath(); + File cPath = null; + for( String path : classpath ) { + File f = new File(path); + if( !f.isAbsolute() ) + continue; + if( absPath.startsWith(f.getCanonicalPath()) ) { + cPath = f.getCanonicalFile(); + break; + } + } + if( cPath==null ) + throw new RuntimeException(dir+" not in classpath"); + StringBuilder clsPathBuf = new StringBuilder(); + StringBuilder topBuf = new StringBuilder( "." ); + for( File f=dirF.getCanonicalFile(); !f.equals(cPath); f=f.getParentFile() ) { + clsPathBuf.insert(0, f.getName() + '.' ); + topBuf.append( File.separator + ".." ); + } + String clsPath = clsPathBuf.toString(); + String top = topBuf.toString(); + for( int i=0; i<a.length; i++ ) { + String s = a[i]; + out.println(); + out.println(s+"_Stub.class: "+s+".class"); + out.println("\trmic -d '"+top+"' "+clsPath+s); + } + String[] subs2 = (String[])dirsRmi.toArray(new String[0]); + int n2 = subs2.length; + for( int i=0; i<n2; i++ ) + out.println("\tcd `pwd`/"+subs2[i]+"; make _rmi"); + } + + rtn.jjt = !jjt.isEmpty() || !dirsJjt.isEmpty(); + out.println(); + out.print("_jjt: "); + if( rtn.jjt ) { + a = (String[])jjt.toArray(new String[0]); + for( int i=0; i<a.length; i++ ) { + out.println("\\"); + out.print("\t\t"+a[i]+".jjt_done "); + } + out.println(); + String[] subs2 = (String[])dirsJjt.toArray(new String[0]); + int n2 = subs2.length; + for( int i=0; i<n2; i++ ) + out.println("\tcd `pwd`/"+subs2[i]+"; make _jjt"); + } + + out.println(); + out.println("clean:"); + out.print("\trm -f *.class *.jjt_done"); + a = (String[])jtp.toArray(new String[0]); + for( int i=0; i<a.length; i++ ) { + out.print(" "+a[i]+".java"); + } + a = (String[])jmp.toArray(new String[0]); + for( int i=0; i<a.length; i++ ) { + out.print(" "+a[i]+".java"); + } + out.println(); + for( int i=0; i<n; i++ ) + out.println("\tcd `pwd`/"+subs[i]+"; make clean"); + out.println(); + out.close(); + System.out.println(dir); + + return rtn; + } + + static PrintWriter init(String dir) + throws IOException + { + PrintWriter out = new PrintWriter(new BufferedOutputStream( + new FileOutputStream(dir+"/Makefile") + )); + out.println("# Makefile created on "+new Date()+" by Mmake"); + out.println(); + out.println(".SUFFIXES: .jtp .jmp .java .class .jjt .jjt_done"); + out.println(); + out.println(".java.class:"); + out.println("\t"+compiler+" $<"); + out.println(); + out.println(".jtp.java:"); + out.println("\tjava fschmidt.tools.Jtp $<"); + out.println(); + out.println(".jmp.java:"); + out.println("\tjava fschmidt.tools.Jmp $<"); + out.println(); + out.println(".jjt.jjt_done:"); + out.println("\tjava jjtree $<"); + out.println("\tjava javacc $*.jj"); + out.println("\ttouch $@"); + out.println(); + out.println("all: _jjt _java _src _rmi"); + out.println(); + out.println("hlp:"); + out.println("\t@echo \"Usage: make [all|clean]\""); + out.println(); + out.print("_src: "); + return out; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/Packages.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,92 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools; + +import java.io.File; +import java.util.Set; +import java.util.HashSet; +import java.util.Arrays; +import java.util.regex.Pattern; +import fschmidt.util.java.ObjectUtils; + + +/** + * Lists nextag packages. Written for javadoc and used like: + * <pre> + * javadoc `java fschmidt.tools.Packages` + * </pre> + * + * @author frank + */ +public final class Packages { + + public static void main (String args[]) { + Set<String> list = new HashSet<String>(); + for( int i=0; i<args.length; i++ ) { + pkgs(list,args[i]); + } + String[] a = (String[])list.toArray(new String[0]); + for( int i=0; i<a.length; i++ ) { + System.out.println(a[i]); + } + } + + static void pkgs(Set<String> list,String pkg) { + String[] dirs = toDirs(pkg); + boolean hasFiles = false; + for( int j=0; j<dirs.length; j++ ) { + String dir = dirs[j]; + String[] ls = new File(dir).list(); + if( ls == null ) + continue; + String[] subs = new String[ls.length]; + for( int i=0; i<ls.length; i++ ) { + File f = new File(dir + File.separator + ls[i]); + if( f.isDirectory() ) { + pkgs( list, pkg + "." + ls[i] ); + } + if( f.getName().endsWith(".java") ) + hasFiles = true; + } + } + if( hasFiles ) + list.add(pkg); + } + + static String[] paths = System.getProperty("java.class.path").split(Pattern.quote(File.pathSeparator),-1); + + static String[] toDirs(String pkg) { + String relPath = ObjectUtils.join( + Arrays.asList(pkg.split("\\.",-1)), File.separator + ); + Set<String> list = new HashSet<String>(); + for( int i=0; i<paths.length; i++ ) { + String path = paths[i]; + if( !path.endsWith(File.separator) ) + path += File.separator; + if( new File(path+relPath).exists() ) + list.add(path+relPath); + } + return (String[])list.toArray(new String[0]); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/Replace.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,51 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools; + +import java.io.InputStreamReader; +import java.io.File; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import fschmidt.util.java.IoUtils; + + +public final class Replace { + public static void main(String args[]) throws Exception { + if( args.length==0 ) { + System.err.println("usage:\njava fschmidt.tools.Replace [pattern] [files...] <<end\n[replacement_text]\nend"); + return; + } + Pattern ptn = Pattern.compile(args[0]); + String replacement = IoUtils.readAll(new InputStreamReader(System.in)); + for( int i=1; i<args.length; i++ ) { + File file = new File(args[i]); + String content = IoUtils.read(file); + Matcher m = ptn.matcher(content); + if( m.find() ) { + System.out.println("replacing "+file); + content = content.substring(0,m.start(1)) + replacement + content.substring(m.end(1)); + IoUtils.write(file,content); + } + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/TcpProxy.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,91 @@ +/* +Copyright (c) 2009 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools; + +import fschmidt.util.java.IoUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + + +public final class TcpProxy { + private static final Logger logger = LoggerFactory.getLogger(TcpProxy.class); + + private static final Executor exec = Executors.newCachedThreadPool(); + + // never returns + public static void proxy(SocketAddress from,final SocketAddress to) throws IOException { + final ServerSocket server = new ServerSocket(); + server.bind(from); + while(true) { + final Socket client = server.accept(); + exec.execute(new Runnable(){public void run() { + final Socket proxy = new Socket(); + try { + proxy.connect(to); + exec.execute(new Runnable(){public void run() { + try { + IoUtils.copyAll( proxy.getInputStream(), client.getOutputStream() ); + } catch(IOException e) { + logger.debug("",e); + } + }}); + IoUtils.copyAll( client.getInputStream(), proxy.getOutputStream() ); + } catch(IOException e) { + logger.debug("",e); + } finally { + try { + proxy.close(); + } catch(IOException e) {} + try { + client.close(); + } catch(IOException e) {} + } + }}); + } + } + + private static SocketAddress parseSocketAddress(String s) { + String[] a = s.split(":"); + return new InetSocketAddress( a[0], Integer.parseInt(a[1]) ); + } + + public static void proxy(String from,String to) throws IOException { + proxy(parseSocketAddress(from), parseSocketAddress(to)); + } + + // better to write your own main() + public static void main(String[] args) throws IOException { + org.apache.log4j.Logger.getRootLogger().setLevel(org.apache.log4j.Level.INFO); + org.apache.log4j.BasicConfigurator.configure(); + proxy(args[0], args[1]); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/web/Cookies.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,79 @@ + +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools.web; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Cookie; +import fschmidt.util.servlet.ServletUtils; + + +public class Cookies extends HttpServlet { + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + ServletOutputStream out = response.getOutputStream(); + response.setHeader("cache-control","no-cache"); + + out.print( "\r\n<html>\r\n<head>\r\n<title>cookies</title>\r\n</head>\r\n<body>\r\n" ); + + Cookie[] cookies = request.getCookies(); + if( cookies == null ) { + + out.print( "\r\nno cookies\r\n" ); + + } else { + + out.print( "\r\n<script>\r\ndocument.write(\"<p>document.cookie = \"+document.cookie);\r\n</script>\r\n" ); + + for( int i=0; i<cookies.length; i++ ) { + Cookie cookie = cookies[i]; + String name = cookie.getName(); + + out.print( "\r\n<p>" ); + out.print( (name) ); + out.print( " = " ); + out.print( (cookie.getValue()) ); + out.print( "\r\n" ); + + try { + + out.print( "\r\n-> " ); + out.print( (ServletUtils.getCookieValue(request,name)) ); + out.print( "\r\n" ); + + } catch(RuntimeException e) {} + } + } + + out.print( "\r\n</body>\r\n</html>\r\n" ); + + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/web/Cookies.jtp Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,80 @@ +<% +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools.web; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Cookie; +import fschmidt.util.servlet.ServletUtils; + + +public class Cookies extends HttpServlet { + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + ServletOutputStream out = response.getOutputStream(); + response.setHeader("cache-control","no-cache"); + %> + <html> + <head> + <title>cookies</title> + </head> + <body> + <% + Cookie[] cookies = request.getCookies(); + if( cookies == null ) { + %> + no cookies + <% + } else { + %> + <script> + document.write("<p>document.cookie = "+document.cookie); + </script> + <% + for( int i=0; i<cookies.length; i++ ) { + Cookie cookie = cookies[i]; + String name = cookie.getName(); + %> + <p><%=name%> = <%=cookie.getValue()%> + <% + try { + %> + -> <%=ServletUtils.getCookieValue(request,name)%> + <% + } catch(RuntimeException e) {} + } + } + %> + </body> + </html> + <% + } +} +%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/web/Headers.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,74 @@ + +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools.web; + +import java.io.IOException; +import java.util.Enumeration; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +public class Headers extends HttpServlet { + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + ServletOutputStream out = response.getOutputStream(); + response.setHeader("cache-control","no-cache"); + + out.print( "\r\n<html>\r\n<body>\r\n" ); + + Enumeration names = request.getHeaderNames(); + while( names.hasMoreElements() ) { + String name = (String)names.nextElement(); + String value = request.getHeader(name); + + out.print( "\r\n<p>" ); + out.print( (name) ); + out.print( " = " ); + out.print( (value) ); + out.print( "\r\n" ); + + } + Enumeration params = request.getParameterNames(); + while( params.hasMoreElements() ) { + String name = (String)params.nextElement(); + String value = request.getParameter(name); + + out.print( "\r\n<p>param " ); + out.print( (name) ); + out.print( " = " ); + out.print( (value) ); + out.print( "\r\n" ); + + } + + out.print( "\r\n</body>\r\n</html>\r\n" ); + + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/web/Headers.jtp Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,68 @@ +<% +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools.web; + +import java.io.IOException; +import java.util.Enumeration; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +public class Headers extends HttpServlet { + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + ServletOutputStream out = response.getOutputStream(); + response.setHeader("cache-control","no-cache"); + %> + <html> + <body> + <% + Enumeration names = request.getHeaderNames(); + while( names.hasMoreElements() ) { + String name = (String)names.nextElement(); + String value = request.getHeader(name); + %> + <p><%=name%> = <%=value%> + <% + } + Enumeration params = request.getParameterNames(); + while( params.hasMoreElements() ) { + String name = (String)params.nextElement(); + String value = request.getParameter(name); + %> + <p>param <%=name%> = <%=value%> + <% + } + %> + </body> + </html> + <% + } +} +%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/web/Index.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,46 @@ + +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools.web; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +public class Index extends HttpServlet { + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + ServletOutputStream out = response.getOutputStream(); +// response.setHeader("cache-control","no-cache"); + + out.print( "\r\n<html>\r\n<body>\r\n<p><a href=\"Cookies.jtp\">cookies</a></p>\r\n<p><a href=\"Headers.jtp\">headers</a></p>\r\n</body>\r\n</html>\r\n" ); + + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/web/Index.jtp Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,51 @@ +<% +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools.web; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +public class Index extends HttpServlet { + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + ServletOutputStream out = response.getOutputStream(); +// response.setHeader("cache-control","no-cache"); + %> + <html> + <body> + <p><a href="Cookies.jtp">cookies</a></p> + <p><a href="Headers.jtp">headers</a></p> + </body> + </html> + <% + } +} +%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/web/Upload.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,81 @@ + +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools.web; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FileOutputStream; +import java.util.Arrays; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +//import org.mortbay.servlet.MultiPartRequest; +import fschmidt.util.java.IoUtils; + + +public class Upload extends HttpServlet { + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + ServletOutputStream out = response.getOutputStream(); + + out.print( "\r\n<html>\r\n<body>\r\n" ); + + String fname = request.getParameter("file"); + String contentType = request.getHeader("Content-Type"); + if( contentType==null ) { + + out.print( "\r\n<form method=\"POST\" enctype=\"multipart/form-data\">\r\nFile:\r\n<input name=\"file\" type=\"file\">\r\n<p><input type=\"submit\" value=\"Upload File\">\r\n</form>\r\n" ); + + } else { +/* + MultiPartRequest mpr = new MultiPartRequest(request); + String filename = mpr.getFilename("file"); + InputStream in = mpr.getInputStream("file"); + File file = new File("../upload/"+filename); + OutputStream out2 = new FileOutputStream(file); + IoUtils.copyAll(in,out2); + out2.close(); + in.close(); + + out.print( "\r\nuploaded to " ); + out.print( (file.getCanonicalPath()) ); + out.print( "\r\n" ); + +*/ + + out.print( "unimplemented" ); + + } + + out.print( "\r\n</body>\r\n</html>\r\n" ); + + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/tools/web/Upload.jtp Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,83 @@ +<% +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.tools.web; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FileOutputStream; +import java.util.Arrays; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +//import org.mortbay.servlet.MultiPartRequest; +import fschmidt.util.java.IoUtils; + + +public class Upload extends HttpServlet { + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + ServletOutputStream out = response.getOutputStream(); + %> + <html> + <body> + <% + String fname = request.getParameter("file"); + String contentType = request.getHeader("Content-Type"); + if( contentType==null ) { + %> + <form method="POST" enctype="multipart/form-data"> + File: + <input name="file" type="file"> + <p><input type="submit" value="Upload File"> + </form> + <% + } else { +/* + MultiPartRequest mpr = new MultiPartRequest(request); + String filename = mpr.getFilename("file"); + InputStream in = mpr.getInputStream("file"); + File file = new File("../upload/"+filename); + OutputStream out2 = new FileOutputStream(file); + IoUtils.copyAll(in,out2); + out2.close(); + in.close(); + %> + uploaded to <%=file.getCanonicalPath()%> + <% +*/ + %>unimplemented<% + } + %> + </body> + </html> + <% + } +} +%>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/diff/Diff.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,496 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.diff; + +import java.util.*; + +/* +taken from http://www.incava.org/projects/java-diff/ +*/ + +/** + * Compares two collections, returning a list of the additions, changes, and + * deletions between them. A <code>Comparator</code> may be passed as an + * argument to the constructor, and will thus be used. If not provided, the + * initial value in the <code>a</code> ("from") collection will be looked at to + * see if it supports the <code>Comparable</code> interface. If so, its + * <code>equals</code> and <code>compareTo</code> methods will be invoked on the + * instances in the "from" and "to" collections; otherwise, for speed, hash + * codes from the objects will be used instead for comparison. + * + * <p>The file FileDiff.java shows an example usage of this class, in an + * application similar to the Unix "diff" program.</p> + */ +public class Diff +{ + /** + * The source array, AKA the "from" values. + */ + protected Object[] a; + + /** + * The target array, AKA the "to" values. + */ + protected Object[] b; + + /** + * The list of differences, as <code>Difference</code> instances. + */ + protected List<Difference> diffs = new ArrayList<Difference>(); + + /** + * The pending, uncommitted difference. + */ + private Difference pending; + + /** + * The comparator used, if any. + */ + private Comparator<Object> comparator; + + /** + * The thresholds. + */ + private TreeMap<Integer,Integer> thresh; + + /** + * Constructs the Diff object for the two arrays, using the given comparator. + */ + public Diff(Object[] a, Object[] b, Comparator<Object> comp) + { + this.a = a; + this.b = b; + this.comparator = comp; + this.thresh = null; // created in getLongestCommonSubsequences + } + + /** + * Constructs the Diff object for the two arrays, using the default + * comparison mechanism between the objects, such as <code>equals</code> and + * <code>compareTo</code>. + */ + public Diff(Object[] a, Object[] b) + { + this(a, b, null); + } + + /** + * Constructs the Diff object for the two collections, using the given + * comparator. + */ + public Diff(Collection a, Collection b, Comparator<Object> comp) + { + this(a.toArray(), b.toArray(), comp); + } + + /** + * Constructs the Diff object for the two collections, using the default + * comparison mechanism between the objects, such as <code>equals</code> and + * <code>compareTo</code>. + */ + public Diff(Collection a, Collection b) + { + this(a, b, null); + } + + /** + * Runs diff and returns the results. + */ + public List<Difference> diff() + { + traverseSequences(); + + // add the last difference, if pending: + if (pending != null) { + diffs.add(pending); + } + + return diffs; + } + + /** + * Traverses the sequences, seeking the longest common subsequences, + * invoking the methods <code>finishedA</code>, <code>finishedB</code>, + * <code>onANotB</code>, and <code>onBNotA</code>. + */ + protected void traverseSequences() + { + Integer[] matches = getLongestCommonSubsequences(); + + int lastA = a.length - 1; + int lastB = b.length - 1; + int bi = 0; + int ai; + + int lastMatch = matches.length - 1; + + for (ai = 0; ai <= lastMatch; ++ai) { + Integer bLine = matches[ai]; + + if (bLine == null) { + onANotB(ai, bi); + } + else { + while (bi < bLine.intValue()) { + onBNotA(ai, bi++); + } + + onMatch(ai, bi++); + } + } + + boolean calledFinishA = false; + boolean calledFinishB = false; + + while (ai <= lastA || bi <= lastB) { + + // last A? + if (ai == lastA + 1 && bi <= lastB) { + if (!calledFinishA && callFinishedA()) { + finishedA(lastA); + calledFinishA = true; + } + else { + while (bi <= lastB) { + onBNotA(ai, bi++); + } + } + } + + // last B? + if (bi == lastB + 1 && ai <= lastA) { + if (!calledFinishB && callFinishedB()) { + finishedB(lastB); + calledFinishB = true; + } + else { + while (ai <= lastA) { + onANotB(ai++, bi); + } + } + } + + if (ai <= lastA) { + onANotB(ai++, bi); + } + + if (bi <= lastB) { + onBNotA(ai, bi++); + } + } + } + + /** + * Override and return true in order to have <code>finishedA</code> invoked + * at the last element in the <code>a</code> array. + */ + protected boolean callFinishedA() + { + return false; + } + + /** + * Override and return true in order to have <code>finishedB</code> invoked + * at the last element in the <code>b</code> array. + */ + protected boolean callFinishedB() + { + return false; + } + + /** + * Invoked at the last element in <code>a</code>, if + * <code>callFinishedA</code> returns true. + */ + protected void finishedA(int lastA) + { + } + + /** + * Invoked at the last element in <code>b</code>, if + * <code>callFinishedB</code> returns true. + */ + protected void finishedB(int lastB) + { + } + + /** + * Invoked for elements in <code>a</code> and not in <code>b</code>. + */ + protected void onANotB(int ai, int bi) + { + if (pending == null) { + pending = new Difference(ai, ai, bi, -1); + } + else { + pending.setDeleted(ai); + } + } + + /** + * Invoked for elements in <code>b</code> and not in <code>a</code>. + */ + protected void onBNotA(int ai, int bi) + { + if (pending == null) { + pending = new Difference(ai, -1, bi, bi); + } + else { + pending.setAdded(bi); + } + } + + /** + * Invoked for elements matching in <code>a</code> and <code>b</code>. + */ + protected void onMatch(int ai, int bi) + { + if (pending == null) { + // no current pending + } + else { + diffs.add(pending); + pending = null; + } + } + + /** + * Compares the two objects, using the comparator provided with the + * constructor, if any. + */ + protected boolean equals(Object x, Object y) + { + return comparator == null ? x.equals(y) : comparator.compare(x, y) == 0; + } + + /** + * Returns an array of the longest common subsequences. + */ + public Integer[] getLongestCommonSubsequences() + { + int aStart = 0; + int aEnd = a.length - 1; + + int bStart = 0; + int bEnd = b.length - 1; + + TreeMap<Integer,Integer> matches = new TreeMap<Integer,Integer>(); + + while (aStart <= aEnd && bStart <= bEnd && equals(a[aStart], b[bStart])) { + matches.put(new Integer(aStart++), new Integer(bStart++)); + } + + while (aStart <= aEnd && bStart <= bEnd && equals(a[aEnd], b[bEnd])) { + matches.put(new Integer(aEnd--), new Integer(bEnd--)); + } + + Map<Object,List<Integer>> bMatches = null; + if (comparator == null) { + if (a.length > 0 && a[0] instanceof Comparable) { + // this uses the Comparable interface + bMatches = new TreeMap<Object,List<Integer>>(); + } + else { + // this just uses hashCode() + bMatches = new HashMap<Object,List<Integer>>(); + } + } + else { + // we don't really want them sorted, but this is the only Map + // implementation (as of JDK 1.4) that takes a comparator. + bMatches = new TreeMap<Object,List<Integer>>(comparator); + } + + for (int bi = bStart; bi <= bEnd; ++bi) { + Object element = b[bi]; + Object key = element; + List<Integer> positions = bMatches.get(key); + if (positions == null) { + positions = new ArrayList<Integer>(); + bMatches.put(key, positions); + } + positions.add(new Integer(bi)); + } + + thresh = new TreeMap<Integer,Integer>(); + Map<Object,Object[]> links = new HashMap<Object,Object[]>(); + + for (int i = aStart; i <= aEnd; ++i) { + Object aElement = a[i]; // keygen here. + List<Integer> positions = bMatches.get(aElement); + + if (positions != null) { + Integer k = new Integer(0); + ListIterator<Integer> pit = positions.listIterator(positions.size()); + while (pit.hasPrevious()) { + Integer j = pit.previous(); + + k = insert(j, k); + + if (k == null) { + // nothing + } + else { + Object value = k.intValue() > 0 ? links.get(new Integer(k.intValue() - 1)) : null; + links.put(k, new Object[] { value, new Integer(i), j }); + } + } + } + } + + if (thresh.size() > 0) { + Integer ti = thresh.lastKey(); + Object[] link = links.get(ti); + while (link != null) { + Integer x = (Integer)link[1]; + Integer y = (Integer)link[2]; + matches.put(x, y); + link = (Object[])link[0]; + } + } + + return toArray(matches); + } + + /** + * Converts the map (indexed by java.lang.Integers) into an array. + */ + protected static Integer[] toArray(TreeMap map) + { + int size = map.size() == 0 ? 0 : 1 + ((Integer)map.lastKey()).intValue(); + Integer[] ary = new Integer[size]; + Iterator it = map.keySet().iterator(); + + while (it.hasNext()) { + Integer idx = (Integer)it.next(); + Integer val = (Integer)map.get(idx); + ary[idx.intValue()] = val; + } + return ary; + } + + /** + * Returns whether the integer is not zero (including if it is not null). + */ + protected static boolean isNonzero(Integer i) + { + return i != null && i.intValue() != 0; + } + + /** + * Returns whether the value in the map for the given index is greater than + * the given value. + */ + protected boolean isGreaterThan(Integer index, Integer val) + { + Integer lhs = thresh.get(index); + return lhs != null && val != null && lhs.compareTo(val) > 0; + } + + /** + * Returns whether the value in the map for the given index is less than + * the given value. + */ + protected boolean isLessThan(Integer index, Integer val) + { + Integer lhs = thresh.get(index); + return lhs != null && (val == null || lhs.compareTo(val) < 0); + } + + /** + * Returns the value for the greatest key in the map. + */ + protected Integer getLastValue() + { + return thresh.get(thresh.lastKey()); + } + + /** + * Adds the given value to the "end" of the threshold map, that is, with the + * greatest index/key. + */ + protected void append(Integer value) + { + Integer addIdx = null; + if (thresh.size() == 0) { + addIdx = new Integer(0); + } + else { + Integer lastKey = thresh.lastKey(); + addIdx = new Integer(lastKey.intValue() + 1); + } + thresh.put(addIdx, value); + } + + /** + * Inserts the given values into the threshold map. + */ + protected Integer insert(Integer j, Integer k) + { + if (isNonzero(k) && isGreaterThan(k, j) && isLessThan(new Integer(k.intValue() - 1), j)) { + thresh.put(k, j); + } + else { + int hi = -1; + + if (isNonzero(k)) { + hi = k.intValue(); + } + else if (thresh.size() > 0) { + hi = thresh.lastKey().intValue(); + } + + // off the end? + if (hi == -1 || j.compareTo(getLastValue()) > 0) { + append(j); + k = new Integer(hi + 1); + } + else { + // binary search for insertion point: + int lo = 0; + + while (lo <= hi) { + int index = (hi + lo) / 2; + Integer val = thresh.get(new Integer(index)); + int cmp = j.compareTo(val); + + if (cmp == 0) { + return null; + } + else if (cmp > 0) { + lo = index + 1; + } + else { + hi = index - 1; + } + } + + thresh.put(new Integer(lo), j); + k = new Integer(lo); + } + } + + return k; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/diff/Difference.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,160 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.diff; + +/* +taken from http://www.incava.org/projects/java-diff/ +*/ + +/** + * Represents a difference, as used in <code>Diff</code>. A difference consists + * of two pairs of starting and ending points, each pair representing either the + * "from" or the "to" collection passed to <code>Diff</code>. If an ending point + * is -1, then the difference was either a deletion or an addition. For example, + * if <code>getDeletedEnd()</code> returns -1, then the difference represents an + * addition. + */ +public class Difference +{ + public static final int NONE = -1; + + /** + * The point at which the deletion starts. + */ + private int delStart = NONE; + + /** + * The point at which the deletion ends. + */ + private int delEnd = NONE; + + /** + * The point at which the addition starts. + */ + private int addStart = NONE; + + /** + * The point at which the addition ends. + */ + private int addEnd = NONE; + + /** + * Creates the difference for the given start and end points for the + * deletion and addition. + */ + public Difference(int delStart, int delEnd, int addStart, int addEnd) + { + this.delStart = delStart; + this.delEnd = delEnd; + this.addStart = addStart; + this.addEnd = addEnd; + } + + /** + * The point at which the deletion starts, if any. A value equal to + * <code>NONE</code> means this is an addition. + */ + public int getDeletedStart() + { + return delStart; + } + + /** + * The point at which the deletion ends, if any. A value equal to + * <code>NONE</code> means this is an addition. + */ + public int getDeletedEnd() + { + return delEnd; + } + + /** + * The point at which the addition starts, if any. A value equal to + * <code>NONE</code> means this must be an addition. + */ + public int getAddedStart() + { + return addStart; + } + + /** + * The point at which the addition ends, if any. A value equal to + * <code>NONE</code> means this must be an addition. + */ + public int getAddedEnd() + { + return addEnd; + } + + /** + * Sets the point as deleted. The start and end points will be modified to + * include the given line. + */ + public void setDeleted(int line) + { + delStart = Math.min(line, delStart); + delEnd = Math.max(line, delEnd); + } + + /** + * Sets the point as added. The start and end points will be modified to + * include the given line. + */ + public void setAdded(int line) + { + addStart = Math.min(line, addStart); + addEnd = Math.max(line, addEnd); + } + + /** + * Compares this object to the other for equality. Both objects must be of + * type Difference, with the same starting and ending points. + */ + public boolean equals(Object obj) + { + if (obj instanceof Difference) { + Difference other = (Difference)obj; + + return (delStart == other.delStart && + delEnd == other.delEnd && + addStart == other.addStart && + addEnd == other.addEnd); + } + else { + return false; + } + } + + /** + * Returns a string representation of this difference. + */ + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append("del: [" + delStart + ", " + delEnd + "]"); + buf.append(" "); + buf.append("add: [" + addStart + ", " + addEnd + "]"); + return buf.toString(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/executor/AbstractThreadPool.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,120 @@ +package fschmidt.util.executor; + +import java.util.List; +import java.util.Queue; +import java.util.LinkedList; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +abstract class AbstractThreadPool implements RunnableWrapper { + private static final Logger logger = LoggerFactory.getLogger(AbstractThreadPool.class); + + private final Thread[] threads; + private boolean isRunning = true; + private final AtomicInteger activeCount = new AtomicInteger(0); + private final List<RunnableWrapper> runnableWrappers = new CopyOnWriteArrayList<RunnableWrapper>(); + + private final Runnable worker = new Runnable() { + + public void run() { + Runnable command; + while( (command = getCommand()) != null ) { + activeCount.incrementAndGet(); + try { + command.run(); + } catch(RuntimeException e) { + logger.error("",e); + } catch(Error e) { + logger.error("",e); + } finally { + activeCount.decrementAndGet(); + } + } + } + + }; + + public AbstractThreadPool(int size) { + threads = new Thread[size]; + } + + final void start() { + for( int i=0; i<threads.length; i++ ) { + Thread thread = new Thread(worker); + thread.start(); + threads[i] = thread; + } + } + + public final void addRunnableWrapper(RunnableWrapper wrapper) { + runnableWrappers.add(wrapper); + } + + @Override final public Runnable wrap(Runnable command) { + for( RunnableWrapper wrapper : runnableWrappers ) { + command = wrapper.wrap(command); + } + return command; + } + + boolean isRunning() { + return isRunning; + } + + abstract Runnable getCommand(); + + public final synchronized void shutdown() { + isRunning = false; + notifyAll(); + } + + public final boolean isTerminated() { + for( Thread thread : threads ) { + if( thread.isAlive() ) + return false; + } + return true; + } + + public final void join() throws InterruptedException { + shutdown(); + for( Thread thread : threads ) { + thread.join(); + } + } + + public final void join(long timeoutMillis) throws InterruptedException { + long until = System.currentTimeMillis() + timeoutMillis; + long now; + int i = 0; + while( i < threads.length && (now = System.currentTimeMillis()) < until ) { + threads[i].join(until-now); + i++; + } + } + + public final boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + long timeoutMillis = unit.toMillis(timeout); + join(timeoutMillis); + return isTerminated(); + } + + public final int getPoolSize() { + return threads.length; + } + + public final int getActiveCount() { + return activeCount.get(); + } + + public abstract int getQueueSize(); + + public final Thread[] getThreads() { + return threads; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/executor/JettyThreadPool.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +package fschmidt.util.executor; + + +public final class JettyThreadPool implements org.eclipse.jetty.util.thread.ThreadPool { + private final ThreadPool threadPool; + + public JettyThreadPool(ThreadPool threadPool) { + this.threadPool = threadPool; + } + + @Override public boolean dispatch(Runnable job) { + return threadPool.dispatch(job); + } + + @Override public void join() throws InterruptedException { + threadPool.join(); + } + + @Override public int getThreads() { + return threadPool.getPoolSize(); + } + + @Override public int getIdleThreads() { + return threadPool.getPoolSize() - threadPool.getActiveCount(); + } + + @Override public boolean isLowOnThreads() { + return getIdleThreads() == 0; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/executor/RunnableWrapper.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,6 @@ +package fschmidt.util.executor; + + +public interface RunnableWrapper { + public Runnable wrap(Runnable command); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/executor/ScheduledThreadPool.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,61 @@ +package fschmidt.util.executor; + +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public final class ScheduledThreadPool extends AbstractThreadPool { + private static final Logger logger = LoggerFactory.getLogger(ScheduledThreadPool.class); + + private final SortedMap<Long,Runnable> queue = new TreeMap<Long,Runnable>(); + + public ScheduledThreadPool(int size) { + super(size); + start(); + } + + public final synchronized void schedule(Runnable command,long delay,TimeUnit unit) { + if( !isRunning() ) + return; + long when = System.currentTimeMillis() + unit.toMillis(delay); + command = wrap(command); + while(true) { + command = queue.put(when,command); + if( command == null ) + break; + when++; + } + notify(); + } + + @Override synchronized Runnable getCommand() { + while(true) { + try { + while( queue.isEmpty() ) { + if( !isRunning() ) + return null; + wait(); + } + long first = queue.firstKey(); + long now = System.currentTimeMillis(); + if( first <= now ) + return queue.remove(first); + if( !isRunning() ) + return null; + wait( first - now ); + } catch(InterruptedException e) { + logger.error("",e); + } + } + } + + @Override public final synchronized int getQueueSize() { + return queue.size(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/executor/ThreadPool.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,52 @@ +package fschmidt.util.executor; + +import java.util.Queue; +import java.util.LinkedList; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public final class ThreadPool extends AbstractThreadPool implements Executor { + private static final Logger logger = LoggerFactory.getLogger(ThreadPool.class); + + private final Queue<Runnable> queue = new LinkedList<Runnable>(); + + public ThreadPool(int size) { + super(size); + start(); + } + + @Override public final void execute(Runnable command) { + dispatch(command); + } + + public final synchronized boolean dispatch(Runnable command) { + if( !isRunning() ) + return false; + queue.add(wrap(command)); + notify(); + return true; + } + + @Override synchronized Runnable getCommand() { + Runnable command; + while( (command = queue.poll()) == null ) { + if( !isRunning() ) + return null; + try { + wait(); + } catch(InterruptedException e) { + logger.error("",e); + } + } + return command; + } + + @Override public final synchronized int getQueueSize() { + return queue.size(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/executor/ThreadTimer.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,32 @@ +package fschmidt.util.executor; + +import java.util.Date; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.BlockingQueue; + + +public final class ThreadTimer implements RunnableWrapper { + private final Map<Thread,Date> map = new ConcurrentHashMap<Thread,Date>(); + + @Override public Runnable wrap(final Runnable command) { + return new Runnable(){public void run(){ + map.put(Thread.currentThread(),new Date()); + try { + command.run(); + } finally { + map.remove(Thread.currentThread()); + } + }}; + } + + public Date whenRun(Thread thread) { + return map.get(thread); + } + + public Set<Thread> getThreads() { + return map.keySet(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ArrayStack.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,35 @@ +package fschmidt.util.java; + +import java.util.Collection; +import java.util.List; +import java.util.ArrayList; +import java.util.EmptyStackException; + + +public class ArrayStack<E> extends ArrayList<E> implements Stack<E> { + + public ArrayStack() {} + + public ArrayStack(Collection<E> c) { + super(c); + } + + @Override public void push(E item) { + add(item); + } + + @Override public E pop() throws EmptyStackException { + int len = size(); + if (len == 0) + throw new EmptyStackException(); + return remove(len - 1); + } + + @Override public E peek() throws EmptyStackException { + int len = size(); + if (len == 0) + throw new EmptyStackException(); + return get(len - 1); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ArrayUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,52 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + + +public final class ArrayUtils { + private ArrayUtils() {} // never + + public static void swap( Object[] a, int i, int j ) { + Object t = a[i]; + a[i] = a[j]; + a[j] = t; + } + + public static void reverse( Object[] a ) { + for( int i=0, j=a.length-1; i<j; i++, j-- ) { + swap(a,i,j); + } + } + + public static String unique(String[] a) { + return (String)unique((Object[])a); + } + + public static Object unique(Object[] a) { + if( a==null ) + return null; + if( a.length > 1 ) + throw new IllegalArgumentException("array not unique"); + return a.length==0 ? null : a[0]; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Base64.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,278 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +/** + * Base64 encoding and decoding (MIME spec RFC 1521). + */ +public class Base64 { + + /** + * Stand alone test + */ + public static void main(String[] args) + throws Exception { + if (args.length < 2) { + System.out.println("Usage: Base64 <encode/decode> <inString>"); + System.exit(0); + } + + String inString = args[1]; + System.out.println("InString = " + inString); + + if (args[0].equalsIgnoreCase("encode")) { + + String encodedString = Base64.encode(inString.getBytes()); + System.out.println("EncodedString = " + encodedString); + + } else { + String outString = new String(Base64.decode(inString)); + System.out.println("DecodedString = " + outString); + } + } + + /** + * Base64-encode binary data. + * + * @param bytes A buffer holding the data to encode. + * @return String String containing the encoded data + */ + public static String encode(byte[] bytes) { + return encode(bytes, false); + } + + /** + * base64-encode binary data. + * + * @param bytes A buffer holding the data to encode. + * @param lineBreaks true if the encoded data will be broken + * into 64-character lines with CRLF pairs. + * @return String String containing the encoded data + */ + public static String encode(byte[] bytes, + boolean lineBreaks) { + + /* slightly bigger buffer than we usually need */ + StringBuilder buf= new StringBuilder((int)(bytes.length * 1.4)); + + int temp = 0; /* holder for the current group of 24 bits */ + int charsWritten = 0; /* count of char written (to current line if lineBreaks == true) */ + int bytesRead = 0; /* count of bytes read in the current 24-bit group */ + for(int index = 0; index < bytes.length; ++index) { + + temp <<= 8; + temp |= (((int)bytes[index]) & EIGHT_BITS); + ++bytesRead; + if(bytesRead == 3) { + /* The lower 3 bytes of temp now hold the 24 bits of a + * translation group. Write them out as 4 characters + * that represent 6 bits each. + */ + buf.append(valueToChar[(temp >> 18) & SIX_BITS]); + buf.append(valueToChar[(temp >> 12) & SIX_BITS]); + buf.append(valueToChar[(temp >> 6) & SIX_BITS]); + buf.append(valueToChar[(temp >> 0) & SIX_BITS]); + temp = 0; + bytesRead = 0; + charsWritten += 4; + if(lineBreaks && charsWritten >= 64) { + buf.append(CRLF); + charsWritten = 0; + } + } + } + + // The input byte[] is exhaused. + // If there were fewer than 3 bytes in the last translation group, + // we must complete the output and pad it to a multiple of 4 chars. + + switch(bytesRead){ + case 1: + // The low 8 bits of temp take 2 characters to represent. + buf.append(valueToChar[(temp >> 2) & SIX_BITS]); + buf.append(valueToChar[(temp << 4) & SIX_BITS]); + buf.append('='); // padding + buf.append('='); // padding + if(lineBreaks) { + buf.append(CRLF); + } + break; + + case 2: + // The low 16 bits of temp take 3 characters to represent + buf.append(valueToChar[(temp >> 10) & SIX_BITS]); + buf.append(valueToChar[(temp >> 4) & SIX_BITS]); + buf.append(valueToChar[(temp << 2) & SIX_BITS]); + buf.append('='); // padding + if(lineBreaks){ + buf.append(CRLF); + } + break; + + default: + // There were exactly 3 bytes in the last group. No padding needed. + break; + } + if(charsWritten > 0 && lineBreaks) { + buf.append(CRLF); + } + return buf.toString(); + } + + /** + * Decode a base64-encoded string into binary data.<P> + * + * Ignores (skips over) carriage-return and line-feed characters. + * Stops decoding when it encounters the base64 pad character ('=') + * or when characters are encountered that are not MIME/base64 numerals. + * + * @param mimeStr a String containing the base64-encoded data + * @return a byte[] containing the decoded binary data. + * @exception RuntimeException if the mimeStr parameter violates + * the base64 encoding rules. + */ + public static byte[] decode(String mimeStr) throws RuntimeException { + + int inputLength = mimeStr.length(); + byte decoded[] = new byte[inputLength]; + int temp = 0; // accumulator for 24-bit translation group + int charsRead = 0; // characters read in current translation group + int bytesWritten = 0; // count of bytes written into decoded[] + char currChar; // current character from mimeStr + byte currByte; // base64 value represented by currChar + int equalCount = 0; // The number of '=' characters we've encountered. + + for (int i = 0; i < inputLength; ++i) { + currChar = mimeStr.charAt(i); + if (currChar == CR || currChar == LF) { + //Skip over carriage return or line-feed character. + continue; + } + try { + currByte = charToValue[(byte)currChar]; + } catch(ArrayIndexOutOfBoundsException obe) { + currByte = BAD_VAL; + } + if (currByte == BAD_VAL) { + if (charsRead == 0) { + /* + * First character of a translation group is a + * non-base64 character. We have presumably + * reached the end of base64 encoded data. + */ + break; + } else if (currChar == '=' & charsRead >= 2) { + // Only the 3d and 4th characters may be '=' + currByte = 0; + ++equalCount; + } else { + //String[] errArgs = new String[]{"Base64"}; + throw new InvalidCharEncodedStringException(); //RuntimeException("E_INVALID_CHAR_ENCODED_STRING"); + } + } + temp <<= 6; + temp |= currByte; + ++charsRead; + if (charsRead == 4) { + /* + * temp has all 24 bits of the translation group, + * write the bytes to decode[] + */ + decoded[bytesWritten++]= (byte)(temp >> 16); + if(equalCount < 2) { + decoded[bytesWritten++]= (byte)(temp >> 8); + if(equalCount < 1) { + decoded[bytesWritten++]= (byte)(temp >> 0); + } + } + charsRead = 0; + temp = 0; + if (equalCount > 0) { + // Encoded data is not permitted after an '=', we must be done. + break; + } + } + } // end for == end of string + + // Trim array to exact length + byte result[] = new byte[bytesWritten]; + System.arraycopy(decoded, 0, result, 0, bytesWritten); + return result; + } + + private static final byte BAD_VAL = (byte)0xFF; + + private static final int SIX_BITS = 0x3F; + private static final int EIGHT_BITS = 0xFF; + + private static final char CR = '\r'; + private static final char LF = '\n'; + + private static final String CRLF = "\r\n"; + + /** + * Value to be encoded is an index into this array, so the + * character representation of byte value = valueToChar[value] + */ + private static final char valueToChar[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /** + * The ASCII value of a character is an index into this array, so that + * decoded byte value = charToValue[(byte)(character)]. <P> + * + * Characters that are not valid base64 numerals are detected + * one of two ways: either the expression above yeilds BAD_VAL + * or the array access gives an ArrayIndexOutOfBoundsException. <P> + * + */ + private static final byte charToValue[] = { + BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, + BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, + BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, + BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, + BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, + BAD_VAL, BAD_VAL, BAD_VAL, 62, BAD_VAL, BAD_VAL, BAD_VAL, 63, + 52, 53, 54, 55, 56, 57, 58, 59, + 60, 61, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, + BAD_VAL, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, + BAD_VAL, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL, BAD_VAL + }; + + public static final class InvalidCharEncodedStringException extends RuntimeException {} + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/BasicRMIClientSocketFactory.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,44 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.io.IOException; +import java.io.Serializable; +import java.net.Socket; +import java.rmi.server.RMIClientSocketFactory; + + +public class BasicRMIClientSocketFactory implements RMIClientSocketFactory, Serializable { + + public Socket createSocket(String host,int port) throws IOException { + return new Socket(host,port); + } + + public boolean equals(Object obj) { + return obj!=null && getClass().equals(obj.getClass()); + } + + public int hashCode() { + return getClass().hashCode(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/BasicRMIServerSocketFactory.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,44 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.io.IOException; +import java.io.Serializable; +import java.net.ServerSocket; +import java.rmi.server.RMIServerSocketFactory; + + +public class BasicRMIServerSocketFactory implements RMIServerSocketFactory, Serializable { + + public ServerSocket createServerSocket(int port) throws IOException { + return new ServerSocket(port); + } + + public boolean equals(Object obj) { + return obj!=null && getClass().equals(obj.getClass()); + } + + public int hashCode() { + return getClass().hashCode(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ClassUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,65 @@ +package fschmidt.util.java; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + + +public final class ClassUtils { + + private ClassUtils() {} // never + + private static final class Key { + private final String name; + private final Class[] parameterTypes; + + private Key(Method m) { + this(m.getName(),m.getParameterTypes()); + } + + private Key(String name,Class[] parameterTypes) { + this.name = name; + this.parameterTypes = parameterTypes; + } + + public boolean equals(Object obj) { + if( !(obj instanceof Key) ) + return false; + Key key = (Key)obj; + return key.name.equals(name) && Arrays.equals(key.parameterTypes,parameterTypes); + } + + public int hashCode() { + return name.hashCode()+31*Arrays.hashCode(parameterTypes); + } + } + + private static final Map<Class,FutureValue<Map<Key,Method>>> cache = new WeakHashMap<Class,FutureValue<Map<Key,Method>>>(); + + // return null if not found + public static Method getMethod(final Class cls,String name,Class... parameterTypes) { + FutureValue<Map<Key,Method>> ft; + synchronized(cache) { + ft = cache.get(cls); + if( ft==null ) { + ft = new FutureValue<Map<Key,Method>>() { + protected Map<Key,Method> compute() { + Map<Key,Method> map = new HashMap<Key,Method>(); + for( Method m : cls.getMethods() ) { + Method m2 = map.put(new Key(m),m); + if( m2 != null ) + throw new RuntimeException("duplicate methods "+m+" and "+m2); + } + return map; + } + }; + cache.put(cls,ft); + } + } + Key key = new Key(name,parameterTypes); + return ft.get().get(key); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/CollectionUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,78 @@ +package fschmidt.util.java; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.HashSet; +import java.util.LinkedHashSet; + + +public final class CollectionUtils { + private CollectionUtils() {} // never + + public static <K,V> Map<K,V> optimizeMap(Map<K,V> map) { + switch( map.size() ) { + case 0: + return Collections.emptyMap(); + case 1: + Map.Entry<K,V> entry = map.entrySet().iterator().next(); + return Collections.singletonMap(entry.getKey(),entry.getValue()); + default: + return new HashMap<K,V>(map); + } + } + + public static <T> List<T> optimizeList(List<T> list) { + switch( list.size() ) { + case 0: + return Collections.emptyList(); + case 1: + return Collections.singletonList(list.get(0)); + default: + return new ArrayList<T>(list); + } + } + + public static <T> Set<T> optimizeSet(Set<T> set) { + switch( set.size() ) { + case 0: + return Collections.emptySet(); + case 1: + return Collections.singleton(set.iterator().next()); + default: + return new HashSet<T>(set); + } + } + + public static <T> Set<T> optimizeLinkedSet(Set<T> set) { + switch( set.size() ) { + case 0: + return Collections.emptySet(); + case 1: + return Collections.singleton(set.iterator().next()); + default: + return new LinkedHashSet<T>(set); + } + } + + public static boolean intersects(Set set,Iterable col) { + for( Object obj : col ) { + if( set.contains(obj) ) + return true; + } + return false; + } + + public static boolean intersects(Set set,Object[] col) { + for( Object obj : col ) { + if( set.contains(obj) ) + return true; + } + return false; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Computable.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,6 @@ +package fschmidt.util.java; + + +public interface Computable<A,V> { + public V get(A arg) throws ComputationException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ComputationException.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,22 @@ +package fschmidt.util.java; + +import java.util.concurrent.ExecutionException; + + +public final class ComputationException extends RuntimeException { + + public ComputationException(Exception e) { + super(e); + } + + public static ComputationException newInstance(ExecutionException e) { + Throwable cause = e.getCause(); + if( cause instanceof Error ) + throw (Error)cause; + if( cause instanceof RuntimeException ) + throw (RuntimeException)cause; + if( cause instanceof Exception ) + return new ComputationException((Exception)cause); + return new ComputationException(e); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/DateUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,97 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.util.Calendar; +import java.util.Date; + + +public class DateUtils { + private DateUtils() {} // never + + public static Date roundToDay(Date date) { + Calendar cal = Calendar.getInstance(); + cal.setLenient(false); + cal.setTime(date); + Calendar cal2 = Calendar.getInstance(); + cal2.setLenient(false); + cal2.clear(); + cal2.set(cal.get(Calendar.YEAR),cal.get(Calendar.MONTH),cal.get(Calendar.DATE)); + return cal2.getTime(); + } + + public static Date add(Date date,int days,int unit) { + Calendar cal = Calendar.getInstance(); + cal.setLenient(false); + cal.setTime(date); + cal.add(unit,days); + return cal.getTime(); + } + + public static Date addDays(Date date,int days) { + return add(date,days,Calendar.DATE); + } + + public static Date addHours(Date date,int hours) { + return add(date,hours,Calendar.HOUR); + } + + private static final long millisPerHour = 1000L*60L*60L; + private static final long millisPerDay = millisPerHour*24L; + + public static int daysBetween(Date dateFrom,Date dateTo) { + long from = dateFrom.getTime(); + long to = dateTo.getTime(); + long diff = (to - from)/millisPerDay; + return (int)diff; + } + + public static int datesBetween(Date dateFrom,Date dateTo) { + long from = roundToDay(dateFrom).getTime(); + long to = roundToDay(dateTo).getTime(); + long diff = (to - from + millisPerHour)/millisPerDay; + return (int)diff; + } + + public static Date max(Date date1,Date date2) { + return date1.after(date2) ? date1 : date2; + } + + public static Date min(Date date1,Date date2) { + return date1.before(date2) ? date1 : date2; + } + + public static boolean equalToTheSecond(Date date1,Date date2) { + return date1!=null && date2!=null && date1.getTime()/1000 == date2.getTime()/1000; + } + + + public static void main(String[] args) { + Date today = roundToDay(new Date()); + Date date = today; + for( int i=0; i<10; i++ ) { + System.out.println(daysBetween(today,date)); + date = addDays(date,1); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/FastFuture.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,50 @@ +package fschmidt.util.java; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; + + +public final class FastFuture<V> implements Future<V> { + private volatile V result; + private volatile FutureTask<V> futureTask; + + public FastFuture(Callable<V> callable) { + futureTask = new FutureTask<V>(callable); + } + + public void run() { + FutureTask<V> ft = futureTask; + if( ft != null ) { + ft.run(); + } + } + + public boolean cancel(boolean mayInterruptIfRunning) { + throw new UnsupportedOperationException(); + } + + public boolean isCancelled() { + return false; + } + + public boolean isDone() { + return futureTask == null; + } + + public V get() throws InterruptedException, ExecutionException { + FutureTask<V> ft = futureTask; + if( ft != null ) { + result = ft.get(); + futureTask = null; + } + return result; + } + + public V get(long timeout,TimeUnit unit) { + throw new UnsupportedOperationException(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Filter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,28 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + + +public interface Filter<T> { + public boolean ok(T t); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/FutureValue.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,27 @@ +package fschmidt.util.java; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + + +public abstract class FutureValue<V> { + + private final FastFuture<V> ft = new FastFuture<V>(new Callable<V>() { + public V call() throws Exception { + return compute(); + } + }); + + public V get() throws ComputationException { + ft.run(); + try { + return ft.get(); + } catch(InterruptedException e) { + throw new ComputationException(e); + } catch(ExecutionException e) { + throw ComputationException.newInstance(e); + } + } + + protected abstract V compute() throws Exception; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/HtmlUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,178 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.lang.reflect.Field; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + + +public final class HtmlUtils { + private HtmlUtils() {} // never + + public static String htmlEncode(String s) { + char[] a = s.toCharArray(); + StringBuilder buf = new StringBuilder(); + for( int i=0; i<a.length; i++ ) { + char c = a[i]; + switch(c) { + case '&': + buf.append("&"); + break; + case '<': + buf.append("<"); + break; + case '>': + buf.append(">"); + break; + case '"': + buf.append("""); + break; + default: + buf.append(c); + } + } + return buf.toString(); + } + + private static final Pattern entityPtn = Pattern.compile( + "&#(\\d+);" + ); + + public static String htmlDecode(String s) { + StringBuffer buf = new StringBuffer(); + Matcher m = entityPtn.matcher(s); + while( m.find() ) { + String entity = new String(new char[]{(char)Integer.parseInt(m.group(1))}); + m.appendReplacement(buf,entity); + } + m.appendTail(buf); + s = buf.toString(); + s = s.replace(" "," "); + s = s.replace(""","\""); + s = s.replace(">",">"); + s = s.replace("<","<"); + s = s.replace("&","&"); + return s; + } + + public static String javascriptStringEncode(String s) { + char[] a = s.toCharArray(); + StringBuilder buf = new StringBuilder(); + for( int i=0; i<a.length; i++ ) { + char c = a[i]; + switch(c) { + case '\\': + buf.append("\\\\"); + break; + case '\'': + buf.append("\\'"); + break; + case '\"': + buf.append("\\\""); + break; + case '\n': + buf.append("\\n"); + break; + case '\r': + buf.append("\\r"); + break; + case '<': + buf.append("\\x3C"); + break; + case '>': + buf.append("\\x3E"); + break; + case '=': + buf.append("\\x3D"); + break; + default: + buf.append(c); + } + } + return buf.toString(); + } + + public static String breakUp(final String text,int maxSize,boolean hasCharEntities) { + StringBuilder buf = new StringBuilder(); + int n = 0; + int len = text.length(); + for( int i=0; i<len; i++ ) { + char c = text.charAt(i); + if( hasCharEntities && c=='&' ) { + do { + buf.append(c); + c = text.charAt(++i); + } while( c != ';' ); + } else if( Character.isWhitespace(c) ) { + n = 0; + } else { + if( ++n > maxSize ) { + buf.append("<wbr />"); + n = 0; + } + } + buf.append(c); + } + return buf.toString(); + } + + public static String urlEncode(String s) { + try { + return URLEncoder.encode(s,"UTF-8"); + } catch(java.io.UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static String urlDecode(String s) { + try { + return URLDecoder.decode(s,"UTF-8"); + } catch(java.io.UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static String toJson(Object object) { + StringBuilder builder = new StringBuilder("{"); + try { + Field[] fields = object.getClass().getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + Object value = field.get(object); + if (value != null) { + if (builder.length() > 1) + builder.append(','); + builder.append('\"').append(field.getName()).append("\": \"").append(javascriptStringEncode(value.toString())).append("\"\n"); + } + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + builder.append('}'); + return builder.toString(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Identity.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,4 @@ +package fschmidt.util.java; + + +public final class Identity<C> {}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ImageUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,238 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import sun.awt.image.BufferedImageGraphicsConfig; + +import javax.imageio.ImageIO; +import java.awt.AlphaComposite; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.RenderingHints; +import java.awt.Transparency; +import java.awt.image.BufferedImage; +import java.awt.image.BufferedImageOp; +import java.awt.image.ConvolveOp; +import java.awt.image.Kernel; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + + +public final class ImageUtils { + private ImageUtils() {} // never + + public static ImageDetails saveImage(String imagePath, BufferedImage image, String format) + throws IllegalArgumentException, IOException + { + FileOutputStream fos = new FileOutputStream(imagePath); + try { + ImageIO.write(image, format, fos); + } finally { + image.flush(); + fos.close(); + } + return new ImageDetails(image.getHeight(), image.getWidth()); + } + + public static BufferedImage loadImage(String imagePath) + throws IOException, IllegalArgumentException + { + FileInputStream fis = new FileInputStream(imagePath); + try { + return createCompatibleImage(ImageIO.read(fis)); + } finally { + fis.close(); + } + } + + public static BufferedImage getImage(InputStream in) + throws IOException, IllegalArgumentException + { + BufferedImage img = ImageIO.read(in); + return img == null? null : createCompatibleImage(img); + } + + public static void deleteImage(String imagePath) { + File imageFile = new File(imagePath); + if (imageFile.exists()) + imageFile.delete(); + } + + private static BufferedImage fixImage(BufferedImage img) { + // Clone the image using the RGB type to prevent color problems. + BufferedImage temp = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D g2 = temp.createGraphics(); + g2.drawImage(img, 0, 0, null); + g2.dispose(); + return temp; + } + + public static ByteArrayOutputStream asOutputStream(BufferedImage img, String format) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(fixImage(img), format, baos); + return baos; + } + + public static InputStream asInputStream(BufferedImage img, String format) throws IOException { + ByteArrayOutputStream baos = asOutputStream(img, format); + final byte[] bytes = baos.toByteArray(); + return new ByteArrayInputStream(bytes); + } + + public static BufferedImage cropImage(BufferedImage image, int x, int y, int width, int height) + throws IllegalArgumentException, IOException + { + if (x < 0) x = 0; + if (y < 0) y = 0; + if (width > image.getWidth()) + width = image.getWidth(); + if (height > image.getHeight()) + height = image.getHeight(); + if (x + width > image.getWidth()) + x = image.getWidth() - width; + if (y + height > image.getHeight()) + y = image.getHeight() - height; + return image.getSubimage(x, y, width, height); + } + + public static BufferedImage resizeImage(BufferedImage image, int width, int height) { + int type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType(); + BufferedImage resizedImage = new BufferedImage(width, height, type); + Graphics2D g = resizedImage.createGraphics(); + g.setComposite(AlphaComposite.Src); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.drawImage(image, 0, 0, width, height, null); + g.dispose(); + return resizedImage; + } + + public static BufferedImage resizeKeepRatio(BufferedImage img, int width, int height) { + int newWidth = img.getWidth() < img.getHeight()? (int) Math.ceil(img.getWidth() * height / (float) img.getHeight()) : width; + int newHeight = img.getWidth() < img.getHeight()? height : (int) Math.ceil(img.getHeight() * width / (float) img.getWidth()); + if (newWidth > width) { + newWidth = width; + newHeight = img.getHeight() * width / img.getWidth(); + } else if (newHeight > height) { + newWidth = img.getWidth() * height / img.getHeight(); + newHeight = height; + } + return resizeImage(img, newWidth, newHeight); + } + + public static BufferedImage blurImage(BufferedImage image) { + float ninth = 1.0f/9.0f; + float[] blurKernel = { + ninth, ninth, ninth, + ninth, ninth, ninth, + ninth, ninth, ninth + }; + + Map<RenderingHints.Key, Object> map = new HashMap<RenderingHints.Key, Object>(); + map.put(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR); + map.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY); + map.put(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); + RenderingHints hints = new RenderingHints(map); + BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, blurKernel), ConvolveOp.EDGE_NO_OP, hints); + image = fixImage(image); + return op.filter(image, null); + } + + private static BufferedImage createCompatibleImage(BufferedImage image) { + GraphicsConfiguration gc = BufferedImageGraphicsConfig.getConfig(image); + int w = image.getWidth(); + int h = image.getHeight(); + BufferedImage result = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT); + Graphics2D g2 = result.createGraphics(); + g2.drawRenderedImage(image, null); + g2.dispose(); + return result; + } + + public static BufferedImage getThumbnail(BufferedImage img, int width, int height) { + if (img.getWidth() <= width && img.getHeight() <= height) + return img; + else { + int widthLimit = 3 * width; + int heightLimit = 3 * height; + if (img.getWidth() > widthLimit || img.getHeight() > heightLimit) { + img = resizeKeepRatio(img, widthLimit, heightLimit); + img = blurImage(img); + } + return resizeKeepRatio(img, width, height); + } + } + + public static final class ImageDetails { + private int height; + private int width; + + public ImageDetails(int height, int width) { + this.height = height; + this.width = width; + } + + public int getHeight() { return height; } + public int getWidth() { return width; } + } + +/* public static void main(String[] args) { + // 140 x 100 px + try { + testThumbnail("a.png", "a1"); + testThumbnail("b.jpg", "b1"); + testThumbnail("c.png", "c1"); + testThumbnail("d.jpg", "d1"); + testThumbnail("e.jpg", "e1"); + testThumbnail("f.jpg", "f1"); + testThumbnail("g.png", "g1"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private static void testThumbnail(String fileIn, String fileOut) throws IOException { + String format = "jpeg"; + BufferedImage img = loadImage("c:\\" + fileIn); + System.out.println(fileIn + " w:" + img.getWidth() + " h:" + img.getHeight()); + long start = System.currentTimeMillis(); + BufferedImage thumb = getThumbnail(img, 140, 100); + + BufferedImage temp = new BufferedImage(thumb.getWidth(), thumb.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics2D g2 = temp.createGraphics(); + g2.drawImage(thumb, 0, 0, null); + g2.dispose(); + thumb = temp; + + System.out.println("Time = " + (System.currentTimeMillis()-start)); + saveImage("c:\\"+fileOut+'.'+format, thumb, format); + }*/ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Interner.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,65 @@ +package fschmidt.util.java; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentHashMap; + + +public final class Interner<T> { + + private static final class MyReference<T> extends WeakReference<T> { + private final int hash; + + MyReference(T t,ReferenceQueue<T> q) { + super(t,q); + hash = t.hashCode(); + } + + public boolean equals(Object obj) { + if( this==obj ) + return true; + if( !(obj instanceof MyReference) ) + return false; + MyReference ref = (MyReference)obj; + T t = this.get(); + if( t==null ) + return false; + return t.equals(ref.get()); + } + + public int hashCode() { + return hash; + } + } + + private final ConcurrentMap<MyReference<T>,MyReference<T>> map = new ConcurrentHashMap<MyReference<T>,MyReference<T>>(); + private ReferenceQueue<T> queue = new ReferenceQueue<T>(); + + private void sweep() { + while(true) { + Reference<? extends T> ref = queue.poll(); + if( ref == null ) + return; + map.remove(ref); + } + } + + public T intern(T t) { + MyReference<T> ref = new MyReference<T>(t,queue); + while(true) { + MyReference<T> ref2 = map.putIfAbsent(ref,ref); + if( ref2 == null ) { + sweep(); + return t; + } + T t2 = ref2.get(); + if( t2 != null ) { + ref.clear(); + return t2; + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/IoUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,400 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + + +public final class IoUtils { + private IoUtils() {} // never + + private static final int bufSize = 8192; + + public static String readAll(Reader in) + throws IOException + { + char[] a = new char[bufSize]; + StringBuilder buf = new StringBuilder(); + int n; + while( (n=in.read(a)) != -1 ) { + buf.append(a,0,n); + } + return buf.toString(); + } + + public static void copyAll(InputStream in,OutputStream out) + throws IOException + { + byte[] a = new byte[bufSize]; + int n; + while( (n=in.read(a)) != -1 ) { + out.write(a,0,n); + } + } + + public static int copyAllAndCount(InputStream in,OutputStream out) + throws IOException + { + byte[] a = new byte[bufSize]; + int n; + int count = 0; + while( (n=in.read(a)) != -1 ) { + out.write(a,0,n); + count += n; + } + return count; + } + + public static void copyAvailable(InputStream in,OutputStream out) + throws IOException + { + byte[] a = new byte[bufSize]; + while( in.available() > 0 ) { + out.write(a,0,in.read(a)); + } + } + + public static byte[] readAll(InputStream in) + throws IOException + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + copyAll(in,out); + return out.toByteArray(); + } + + public static void writeAll(byte[] a,OutputStream out) + throws IOException + { + copyAll(new ByteArrayInputStream(a),out); + } + + public static byte[] readAll(File file) + throws IOException + { + int len = (int)file.length(); + ByteArrayOutputStream out = new ByteArrayOutputStream(len) { + public byte[] toByteArray() { + return buf; + } + }; + FileInputStream in = new FileInputStream(file); + copyAll(in,out); + in.close(); + return out.toByteArray(); + } + + public static void writeAll(byte[] a,File file) + throws IOException + { + FileOutputStream fos = new FileOutputStream(file); + writeAll(a,fos); + fos.close(); + } + + public static String readPage(String url) + throws IOException + { + return new UrlCall(url).get(); + } + + public static String read(URL url) + throws IOException + { + return new UrlCall(url).get(); + } + + public static String post(String urlS,String postS) + throws IOException + { + return new UrlCall(urlS).post(postS); + } + + public static String hideNull(String s) { + return s==null ? "" : s; + } + + public static boolean delete(File file) { + if( file.isDirectory() ) { + File[] files = file.listFiles(); + for( int i=0; i<files.length; i++ ) { + delete(files[i]); + } + } + return file.delete(); + } + + public static URL getUrlFromResource(String resourceName) { +/* + ClassLoader loader = IoUtils.class.getClassLoader(); + return loader.getResource(resourceName); +*/ + return ClassLoader.getSystemResource(resourceName); + } + + public static String getContentFromResource(String resourceName) + throws IOException + { + URL url = getUrlFromResource(resourceName); + return url==null ? null : read(url); + } + + public static String read(File file) + throws IOException + { + Reader in = newFileReader(file); + String s = readAll(in); + in.close(); + return s; + } + + public static void write(File file,String s) + throws IOException + { + Writer out = newFileWriter(file); + out.write(s); + out.close(); + } + + public static Reader newFileReader(File file) + throws IOException + { + return new InputStreamReader(new FileInputStream(file),Charset.forName("utf-8")); + } + + public static Reader newFileReader(String file) + throws IOException + { + return new InputStreamReader(new FileInputStream(file),Charset.forName("utf-8")); + } + + public static Writer newFileWriter(File file) + throws IOException + { + return new OutputStreamWriter(new FileOutputStream(file),Charset.forName("utf-8")); + } + + public static Writer newFileWriter(String file) + throws IOException + { + return new OutputStreamWriter(new FileOutputStream(file),Charset.forName("utf-8")); + } + + public static int hashCode(InputStream in) + throws IOException + { + int h = 0; + int c; + while( (c=in.read()) != -1 ) { + h = 31*h + c; + } + return h; + } + + public static int hashCode(File file) + throws IOException + { + InputStream in = new BufferedInputStream(new FileInputStream(file)); + int r = hashCode(in); + in.close(); + return r; + } + + public static int compare(InputStream in1,InputStream in2) + throws IOException + { + while(true) { + int c1 = in1.read(); + int c2 = in2.read(); + if( c1 == -1 && c2 == -1 ) + return 0; + if( c1 != c2 ) + return c1 - c2; + } + } + + public static int compare(File file1,File file2) + throws IOException + { + InputStream in1 = new BufferedInputStream(new FileInputStream(file1)); + InputStream in2 = new BufferedInputStream(new FileInputStream(file2)); + int r = compare(in1,in2); + in2.close(); + in1.close(); + return r; + } + + public static void copy(File file1,File file2) + throws IOException + { + InputStream in = new BufferedInputStream(new FileInputStream(file1)); + OutputStream out = new BufferedOutputStream(new FileOutputStream(file2)); + copyAll(in,out); + out.close(); + in.close(); + } + + public static Iterable<URL> getResources(Class classInJarOrDir) { + String classFile = classInJarOrDir.getName().replace('.','/') + ".class"; + return getResources(classFile); + } + + public static Iterable<URL> getResources(String fileName) { + URL classUrl = ClassLoader.getSystemResource(fileName); + String protocol = classUrl.getProtocol(); + if( protocol.equals("file") ) { + File f = new File(classUrl.getFile()).getParentFile(); + for( int i = -1; (i = fileName.indexOf('/',i+1)) != -1; ) { + f = f.getParentFile(); + } + return new FileIterator(f); + } + if( protocol.equals("jar") ) { + String s = classUrl.getFile(); + if( !s.startsWith("file:") ) + throw new RuntimeException("jar isn't file: "+classUrl); + File f = new File(s.substring(5,s.indexOf('!'))); + try { + JarFile jar = new JarFile(f); + return new JarIterator(jar); + } catch(IOException e) { + throw new RuntimeException(e); + } + } + throw new RuntimeException("can't handle protocol: "+protocol); + } + + private static class FileIterator implements Iterator<URL>, Iterable<URL> { + private final File[] files; + private Iterator<URL> childIter = Collections.<URL>emptyList().iterator(); + private URL next; + private int i = -1; + + FileIterator(File dir) { + this.files = dir.listFiles(); + } + + public Iterator<URL> iterator() { + return this; + } + + public boolean hasNext() { + if( next!=null ) + return true; + if( childIter.hasNext() ) { + next = childIter.next(); + return true; + } + while( ++i < files.length ) { + File f = files[i]; + if( f.isFile() ) { + try { + next = f.toURL(); + } catch(MalformedURLException e) { + throw new RuntimeException(e); + } + return true; + } + if( f.isDirectory() ) { + childIter = new FileIterator(f); + if( childIter.hasNext() ) { + next = childIter.next(); + return true; + } + } + } + return false; + } + + public URL next() { + if( next==null ) + throw new NoSuchElementException(); + try { + return next; + } finally { + next = null; + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + private static class JarIterator implements Iterator<URL>, Iterable<URL> { + private final Enumeration<JarEntry> entries; + private final String urlBase; + + JarIterator(JarFile jar) { + this.entries = jar.entries(); + try { + this.urlBase = "jar:" + new File(jar.getName()).toURL().toString() + "!/"; + } catch(MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public Iterator<URL> iterator() { + return this; + } + + public boolean hasNext() { + return entries.hasMoreElements(); + } + + public URL next() { + try { + return new URL( urlBase + entries.nextElement() ); + } catch(MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/IteratorEnumeration.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,44 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.util.Enumeration; +import java.util.Iterator; + + +public final class IteratorEnumeration<T> implements Enumeration<T> { + private final Iterator<T> i; + + public IteratorEnumeration(Iterator<T> i) { + this.i = i; + } + + public boolean hasMoreElements() { + return i.hasNext(); + } + + public T nextElement() { + return i.next(); + } +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/LoggingRunnable.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,49 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public final class LoggingRunnable implements Runnable { + private static final Logger logger = LoggerFactory.getLogger(LoggingRunnable.class); + + private final Runnable r; + + public LoggingRunnable(Runnable r) { + this.r = r; + } + + public void run() { + try { + r.run(); + } catch(RuntimeException e) { + logger.error("exception in runnable",e); + throw e; + } catch(Error e) { + logger.error("error in runnable",e); + System.exit(-1); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/MD5Util.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,27 @@ +package fschmidt.util.java; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class MD5Util { + public static String hex(byte[] array) { + StringBuffer sb = new StringBuffer(); + for (byte anArray : array) { + sb.append(Integer.toHexString((anArray + & 0xFF) | 0x100).substring(1, 3)); + } + return sb.toString(); + } + + public static String md5Hex(String message) { + try { + MessageDigest md = + MessageDigest.getInstance("MD5"); + return hex(md.digest(message.getBytes("CP1252"))); + } catch (NoSuchAlgorithmException e) { + } catch (UnsupportedEncodingException e) { + } + return null; + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Memoizer.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,100 @@ +package fschmidt.util.java; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +// based on Memoizer from Java Concurrency in Practice + +public final class Memoizer<A,V> implements Computable<A,V> { + private final ConcurrentMap<A, Future<V>> cache + = new ConcurrentHashMap<A, Future<V>>(); + private final Computable<A,V> comp; + + public Memoizer(Computable<A,V> comp) { + this.comp = comp; + } + + public V get(final A arg) throws ComputationException { + while (true) { + Future<V> f = cache.get(arg); + if (f == null) { + Callable<V> eval = new Callable<V>() { + public V call() throws Exception { + return comp.get(arg); + } + }; + FastFuture<V> ft = new FastFuture<V>(eval); + f = cache.putIfAbsent(arg, ft); + if (f == null) { + f = ft; + ft.run(); + } + } + try { + return f.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (CancellationException e) { + cache.remove(arg, f); + } catch (ExecutionException e) { + throw ComputationException.newInstance(e); + } + } + } + + public Map<A,V> get(final Collection<A> args) throws ComputationException { + outer: + while (true) { + Map<A,Future<V>> fmap = new HashMap<A,Future<V>>(); + for( final A arg : args ) { + Future<V> f = cache.get(arg); + if (f == null) { + Callable<V> eval = new Callable<V>() { + public V call() throws Exception { + return comp.get(arg); + } + }; + FastFuture<V> ft = new FastFuture<V>(eval); + f = cache.putIfAbsent(arg, ft); + if (f == null) { + f = ft; + ft.run(); + } + } + fmap.put(arg,f); + } + Map<A,V> map = new HashMap<A,V>(); + for( Map.Entry<A,Future<V>> entry : fmap.entrySet() ) { + A arg = entry.getKey(); + Future<V> f = entry.getValue(); + try { + map.put( arg, f.get() ); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (CancellationException e) { + cache.remove(arg, f); + continue outer; + } catch (ExecutionException e) { + throw ComputationException.newInstance(e); + } + } + return map; + } + } + + public void remove(final A arg) { + cache.remove(arg); + } +/* + public void clear() { + cache.clear(); + } +*/ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Money.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,292 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.io.Externalizable; +import java.io.ObjectInput; +import java.io.IOException; +import java.io.ObjectOutput; +import java.io.ObjectInputStream; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.ResultSet; +import java.math.BigDecimal; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import fschmidt.db.DbStorable; + + +public final class Money implements Comparable, Externalizable, DbStorable { + private static final int currentVersion = 0; + private static final long serialVersionUID = 0L; + + private /*final*/ long cents; + + public static Money read(ObjectInput in) + throws IOException + { + if( !in.readBoolean() ) + return null; + return new Money(in.readLong()); + } + + public static void write(ObjectOutput out,Money m) + throws IOException + { + if( m==null ) { + out.writeBoolean(false); + return; + } + out.writeBoolean(true); + out.writeLong(m.cents); + } + + public void writeExternal(ObjectOutput out) + throws IOException + { + out.writeInt(currentVersion); + out.writeLong(cents); + } + + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException + { + int ver = in.readInt(); + switch(ver) { + case 0: + cents = in.readLong(); + break; + default: + throw new RuntimeException(); + } + } + + public Money() {} // for Externalizable + + public Money(ObjectInput in) + throws IOException, ClassNotFoundException + { + cents = in.readInt(); + } + + public Money(long cents) { + this.cents = cents; + } + + private static final Pattern p = Pattern.compile(" *-?\\$? *[0-9,.]+ *"); + private static final Pattern pSub = Pattern.compile("[ \\$,]"); + + public Money(String val) + throws NumberFormatException + { + if( !p.matcher(val).matches() ) + throw new NumberFormatException("invalid format for money: '"+val+"'"); + Matcher mSub = pSub.matcher(val); + val = pSub.matcher(val).replaceAll(""); + cents = Math.round(Float.parseFloat(val)*100); + } + + public Money(double d) { + cents = (long)Math.round( (d *1000) / 10); + } + + public void setField(PreparedStatement stmt,int idx) + throws SQLException + { + stmt.setBigDecimal(idx,toBigDecimal()); + } + + public static void setMoneyNull(PreparedStatement stmt,int idx) + throws SQLException + { + stmt.setNull(idx,java.sql.Types.DECIMAL); + } + + public static void setMoney(PreparedStatement stmt,int idx,Money m) + throws SQLException + { + if( m==null ) { + setMoneyNull(stmt,idx); + } else { + m.setField(stmt,idx); + } + } + + public static Money getMoney(ResultSet rs,String columnName) + throws SQLException + { + Money m = new Money(rs,columnName); + return rs.wasNull() ? null : m; + } + + private Money(ResultSet rs,String columnName) + throws SQLException + { + cents = Math.round(rs.getFloat(columnName)*100); + } + + public boolean equals(Object obj) { + if( obj==null || !(obj instanceof Money) ) + return false; + Money m = (Money)obj; + return cents==m.cents; + } + + public int compareTo(Money val) { + return val != null ? + (cents < val.cents ? -1 : cents==val.cents ? 0 : 1) : + 0; + } + + public int compareTo(Object obj) { + return compareTo( (Money)obj ); + } + + public int hashCode() { + return (int)cents; + } + + public String toString() { + return toString("$"); + } + + public String toString(String currency) { + StringBuilder buf = new StringBuilder(); + buf.append( cents<0 ? -cents : cents ); + while( buf.length() < 3 ) + buf.insert(0,'0'); + buf.insert(buf.length()-2,'.'); + int start = buf.charAt(0)=='-' ? 1 : 0; + for( int i=buf.length()-6; i>start; i-=3 ) + buf.insert(i,','); + buf.insert(0,currency); + if( cents < 0 ) + buf.insert(0,'-'); + return buf.toString(); + } + + public String toSimpleString() { + StringBuilder buf = new StringBuilder(); + buf.append(cents); + while( buf.length() < 3 ) + buf.insert(0,'0'); + buf.insert(buf.length()-2,'.'); + + return buf.toString(); + } + + public boolean isZero() { + return cents==0; + } + + public Money min(Money val) { + return cents < val.cents ? this : val; + } + + public Money max(Money val) { + return cents > val.cents ? this : val; + } + + public Money add(Money val) { + return new Money(cents+val.cents); + } + + public Money subtract(Money val) { + return new Money(cents-val.cents); + } + + public Money multiply(int val) { + return new Money(cents*val); + } + + public Money multiply(float val) { + return new Money(Math.round(cents*val)); + } + + public Money multiply(double val) { + return new Money((long)Math.round(cents*val)); + } + + public double divide(Money val) { + if ((double)val.cents == 0) { + return Double.NaN; + } + return (double)cents/(double)val.cents; + } + + public Money divide(int val) { + return new Money(cents/val); + } + + public Money divideRoundingUp(int val) { + return new Money((cents+val-1)/val); + } + + public Money divide(float val) { + return new Money(Math.round(cents/val)); + } + + public Money divide(double val) { + return new Money((long)Math.round(cents/val)); + } + + public Money inc() { + return new Money(cents+1); + } + + public Money dec() { + return new Money(cents-1); + } + + public Money avg(Money val) { + return new Money((cents+val.cents)/2); + } + + public double doubleValue() { + return cents/100.0; + } + + public BigDecimal toBigDecimal() { + return BigDecimal.valueOf(cents,2); + } + + public long getCents() { + return cents; + } + + + public static final Money ZERO = new Money(0); + public static final Money MAX_VALUE = new Money(Integer.MAX_VALUE); + public static final Money MIN_VALUE = new Money(Integer.MIN_VALUE); + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + } + + public static void main(String[] args) { + System.out.println(new Money("z$1,234.56 ")); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ObjectInputStreamCL.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,49 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.io.*; + + +public class ObjectInputStreamCL extends ObjectInputStream { + private final ClassLoader cl; + + public ObjectInputStreamCL(InputStream in,ClassLoader cl) + throws IOException + { + super(in); + this.cl = cl; + } + + protected Class resolveClass(ObjectStreamClass desc) + throws IOException, ClassNotFoundException + { + String name = desc.getName(); + try { + return Class.forName(name, false, cl); + } catch (ClassNotFoundException ex) { + return super.resolveClass(desc); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ObjectUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,55 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + + +public final class ObjectUtils { + private ObjectUtils() {} // never + + public static boolean equals(Object obj1,Object obj2) { + return obj1==null ? obj2==null : obj1.equals(obj2); + } + + public static String join(Iterable<?> iter) { + StringBuilder buf = new StringBuilder(); + for( Object o : iter ) { + buf.append( o ); + } + return buf.toString(); + } + + public static String join(Iterable<?> iter,String separator) { + StringBuilder buf = new StringBuilder(); + boolean isFirst = true; + for( Object o : iter ) { + if( isFirst ) { + isFirst = false; + } else { + buf.append( separator ); + } + buf.append( o ); + } + return buf.toString(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ProcUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,72 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.io.*; + + +public final class ProcUtils { + private ProcUtils() {} // never + + public static class ProcException extends IOException { + private ProcException(String msg) { + super(msg); + } + } + + public static void checkProc(Process proc) + throws IOException, ProcException + { + try { + proc.waitFor(); + } catch(InterruptedException e) { + throw new RuntimeException(e); + } + int exitVal = proc.exitValue(); + if( exitVal != 0 ) { + Reader err = new InputStreamReader(proc.getErrorStream()); + String error = IoUtils.readAll(err); + err.close(); + throw new ProcException(error); + } + } + + public static String getOutput(Process proc) + throws IOException + { + BufferedReader in = new BufferedReader(new InputStreamReader(proc.getInputStream())); + String s = IoUtils.readAll(in); + in.close(); + return s; + } + + public static String exec(String[] cmd) + throws IOException, ProcException + { + Process proc = Runtime.getRuntime().exec(cmd); + String s = getOutput(proc); + checkProc(proc); + return s; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ProxyClass.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,66 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + + +public class ProxyClass<T> implements InvocationHandler { + protected final T obj; + private final Class<T> cls; + + protected ProxyClass(T obj,Class<T> cls) { + this.obj = obj; + this.cls = cls; + } + + public Object invoke(Method method, Object[] args) + throws Throwable + { + try { + return method.invoke(obj,args); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + } + + public Object invoke(Object proxy,Method method, Object[] args) + throws Throwable + { + return invoke(method,args); + } + + public T newInstance() { + @SuppressWarnings("unchecked") + T rtn = (T)Proxy.newProxyInstance( + cls.getClassLoader(), + new Class[]{cls}, + this + ); + return rtn; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/ProxyIntoThread.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,113 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; + + +public class ProxyIntoThread<T> extends ProxyClass<T> { + private static final Logger logger = LoggerFactory.getLogger(ProxyIntoThread.class); + private final long timeout; + private volatile Thread thread; + private final Object lock = new Object(); + private boolean isCalling = false; + private Method method; + private Object[] args; + private Object rtn; + private Throwable ex; + + public ProxyIntoThread(String threadName,long timeout,final T obj,Class<T> cls) { + super(obj,cls); + this.timeout = timeout; + logger.info("Name = " + threadName + " / timeout = " + timeout + " / isCalling = " + isCalling); + thread = new Thread( + new Runnable(){public void run(){ + synchronized(lock) { + while(thread!=null) { + while( !isCalling ) { + try { + lock.wait(ProxyIntoThread.this.timeout); + } catch(InterruptedException e) { + logger.error("",e); + throw new RuntimeException(e); + } + if( !isCalling ) { + logger.error("timed out"); + throw new RuntimeException("timed out"); + } + } + try { + rtn = invoke(method,args); + } catch(Throwable e) { + ex = e; + } finally { + isCalling = false; + lock.notifyAll(); + } + } + } + }} + , threadName + ); + thread.start(); + } + + public Object invoke(Object proxy,Method method,Object[] args) + throws Throwable + { + synchronized(lock) { + if( thread==null ) + throw new RuntimeException("thread died"); + if( isCalling ) + throw new RuntimeException("Already calling " + this.method.getName()); + this.method = method; + this.args = args; + this.ex = null; + isCalling = true; + lock.notifyAll(); + while( isCalling ) { + try { + lock.wait(timeout); + } catch(InterruptedException e) { + logger.error("",e); + throw new RuntimeException(e); + } + if( isCalling ) { + logger.error("timed out"); + throw new RuntimeException("timed out"); + } + } + if( ex != null ) + throw ex; + return this.rtn; + } + } + + public void stop() { + thread = null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/SimpleCache.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,34 @@ +package fschmidt.util.java; + +import java.util.Map; + + +public final class SimpleCache<A,V> implements Computable<A,V> { + private final Map<A,V> map; + private final Computable<A,V> comp; + + public SimpleCache(Map<A,V> map,Computable<A,V> comp) { + this.map = map; + this.comp = comp; + } + + public synchronized V get(A arg) throws ComputationException { + V val = map.get(arg); + if( val == null ) { + try { + val = comp.get(arg); + } catch(RuntimeException e) { + throw e; + } catch(Exception e) { + throw new ComputationException(e); + } + map.put(arg,val); + } + return val; + } + + public synchronized void remove(A arg) { + map.remove(arg); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/SimpleClassLoader.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,69 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.io.*; + + +public final class SimpleClassLoader extends ClassLoader { + + public interface Filter { + public boolean load(String className); + } + + private final Filter filter; + + public SimpleClassLoader(Filter filter) { + this.filter = filter; + } + + public synchronized Class loadClass(String name) + throws ClassNotFoundException + { + Class c = findLoadedClass(name); + return c != null ? c + : filter.load(name) ? findClass(name) + : getParent().loadClass(name) + ; + } + + protected Class findClass(String name) + throws ClassNotFoundException + { + try { + InputStream in = getResourceAsStream( classToResource(name) ); + if( in==null ) + throw new ClassNotFoundException(name); + byte[] data = IoUtils.readAll(in); + in.close(); + return defineClass(name,data,0,data.length); + } catch(IOException e) { + throw new ClassNotFoundException(name,e); + } + } + + public static String classToResource(String clsName) { + return clsName.replace('.','/') + ".class"; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Sorter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,82 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.util.List; +import java.util.ArrayList; + + +public final class Sorter<K extends Comparable<K>,V> { + private final List<K> keyList = new ArrayList<K>(); + private final List<V> valueList = new ArrayList<V>(); + private final int limit; + + public Sorter() { + this(0); + } + + public Sorter(int limit) { + this.limit = limit; + } + + private int position(K obj) { + int n = keyList.size(); + for( int i=0; i<n; i++ ) { + if( obj.compareTo(keyList.get(i)) <= 0 ) + return i; + } + return n; + } + + public void put(K key,V value) { + int i = position(key); + if( limit>0 && i==limit ) + return; + keyList.add(i,key); + valueList.add(i,value); + if( limit>0 && size()>limit ) + remove(limit); + } + + public List<V> values() { + return valueList; + } + + public int size() { + return valueList.size(); + } + + public void remove(int index) { + keyList.remove(index); + valueList.remove(index); + } + + public V getValue(int index) { + return valueList.get(index); + } + + public K getKey(int index) { + return keyList.get(index); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Stack.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,14 @@ +package fschmidt.util.java; + +import java.util.List; +import java.util.EmptyStackException; + + +/* +java.util.Stack should have been an interface. +*/ +public interface Stack<E> extends List<E> { + public void push(E item); + public E pop() throws EmptyStackException; + public E peek() throws EmptyStackException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/TeeOutputStream.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,65 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.io.*; + + +public final class TeeOutputStream extends OutputStream { + private final OutputStream[] outs; + + public TeeOutputStream(OutputStream[] outs) { + this.outs = outs; + } + + public void write(int b) throws IOException { + for( int i=0; i<outs.length; i++ ) { + outs[i].write(b); + } + } + + public void write(byte b[]) throws IOException { + for( int i=0; i<outs.length; i++ ) { + outs[i].write(b); + } + } + + public void write(byte b[], int off, int len) throws IOException { + for( int i=0; i<outs.length; i++ ) { + outs[i].write(b,off,len); + } + } + + public void flush() throws IOException { + for( int i=0; i<outs.length; i++ ) { + outs[i].flush(); + } + } + + public void close() throws IOException { + for( int i=0; i<outs.length; i++ ) { + outs[i].close(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/TimedCacheMap.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,87 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.Iterator; +import fschmidt.db.util.WeakCacheMap; + + +public class TimedCacheMap<K,V> extends WeakCacheMap<K,V> { + + private static class IdentityRef { + private final Object obj; + + IdentityRef(Object obj) { + this.obj = obj; + } + + public int hashCode() { + return System.identityHashCode(obj); + } + + public boolean equals(Object obj) { + return obj instanceof IdentityRef + && obj==((IdentityRef)obj).obj; + } + } + + private final long timeLimit; + private final Map<IdentityRef,Long> vals = new LinkedHashMap<IdentityRef,Long>(); + + public TimedCacheMap(long timeLimit) { + this.timeLimit = timeLimit; + } + + private void add(V value,long now) { + if( value==null ) + return; + IdentityRef ref = new IdentityRef(value); + vals.remove(ref); + vals.put( ref, now + timeLimit ); + } + + private void sweep(long now) { + for( Iterator<Long> i = vals.values().iterator(); i.hasNext(); ) { + long time = i.next(); + if( time > now ) + return; + i.remove(); + } + } + + public V put(K key, V value) { + long now = System.currentTimeMillis(); + sweep(now); + add(value,now); + return super.put(key,value); + } + + public V get(Object key) { + long now = System.currentTimeMillis(); + V value = super.get(key); + add(value,now); + return value; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/UrlCall.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,76 @@ +package fschmidt.util.java; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.IOException; +import java.net.URLConnection; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Map; +import java.util.HashMap; + + +public final class UrlCall { + public final URLConnection connection; + + public UrlCall(String url) throws IOException { + this(new URL(url)); + } + + public UrlCall(URL url) throws IOException { + connection = url.openConnection(); + } + + public void acceptJson() { + connection.setRequestProperty("accept","application/json"); + } + + public String get() throws IOException { + Reader in = new InputStreamReader(connection.getInputStream()); + String rtn = IoUtils.readAll(in); + in.close(); + return rtn; + } + + public String post(String content,String contentType) throws IOException { + HttpURLConnection connection = (HttpURLConnection)this.connection; + + connection.setRequestProperty("Content-type",contentType); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + + byte[] post = content.getBytes(); + connection.setRequestProperty("Content-Length",Integer.toString(post.length)); + OutputStream out = connection.getOutputStream(); + out.write(post); + out.flush(); + + Reader in; + try { + in = new InputStreamReader(connection.getInputStream()); + } catch(IOException e) { + InputStream is = connection.getErrorStream(); + if( is == null ) + throw e; + in = new InputStreamReader(is); + String msg = IoUtils.readAll(in); + in.close(); + throw new UrlCallException(msg,e); + } + String rtn = IoUtils.readAll(in); + in.close(); + out.close(); + return rtn; + } + + public String post(String content) throws IOException { + return post(content,"application/x-www-form-urlencoded"); + } + + public String postJson(String content) throws IOException { + return post(content,"application/json"); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/UrlCallException.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,11 @@ +package fschmidt.util.java; + +import java.io.IOException; + + +public final class UrlCallException extends IOException { + + UrlCallException(String msg,IOException e) { + super(msg,e); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/Version.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,70 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.util.regex.*; + + +public final class Version implements Comparable { + private final String name; + private final String name2; + private final int[] parts; + private static final Pattern p = Pattern.compile("^[0-9.]+"); + + public Version(String name) { + this.name = name; + Matcher m = p.matcher(name); + if( !m.find() ) + throw new RuntimeException("invalid version: "+name); + this.name2 = m.group(); + String[] a = name2.split("\\."); + parts = new int[a.length]; + for( int i=0; i<a.length; i++ ) { + parts[i] = Integer.parseInt(a[i]); + } + } + + public String toString() { + return name; + } + + public int compareTo(Object o) { + Version v = (Version)o; + int n = Math.min(parts.length,v.parts.length); + for( int i=0; i<n; i++ ) { + int r = parts[i] - v.parts[i]; + if( r != 0 ) + return r; + } + return parts.length - v.parts.length; + } + + public boolean equals(Object o) { + return o instanceof Version && compareTo(o)==0; + } + + public int hashCode() { + return name2.hashCode(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/java/WeakList.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,69 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.java; + +import java.util.List; +import java.util.AbstractList; +import java.util.ArrayList; +import java.lang.ref.WeakReference; + + +public final class WeakList<E> extends AbstractList<E> { + private List<WeakReference<E>> list = new ArrayList<WeakReference<E>>(); + + public void removeNulls() { + while( remove(null) ); + } + + public int size() { + return list.size(); + } + + public E get(int index) { + WeakReference<E> ref = list.get(index); + return ref==null ? null : ref.get(); + } + + public E set(int index,E element) { + WeakReference<E> ref = list.set(index,new WeakReference<E>(element)); + return ref==null ? null : ref.get(); + } + + public boolean add(E element) { + return list.add(new WeakReference<E>(element)); + } + + public void add(int index,E element) { + list.add(index,new WeakReference<E>(element)); + } + + public E remove(int index) { + WeakReference<E> ref = list.remove(index); + return ref==null ? null : ref.get(); + } + + public <T> T[] toArray(T[] a) { + removeNulls(); + return super.toArray(a); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/locks/Locker.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,34 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.locks; + +import java.util.concurrent.TimeUnit; + + +public interface Locker<T> { + public void lock(T obj); +// public void lockInterruptibly(T obj) throws InterruptedException; + public boolean tryLock(T obj); + public boolean tryLock(T obj,long time,TimeUnit unit) throws InterruptedException; + public void unlock(T obj); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/locks/ProxyLock.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,66 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.locks; + + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.Condition; + + +public class ProxyLock implements Lock { + private final Lock lock; + + protected ProxyLock(Lock lock) { + this.lock = lock; + } + + public void lock() { + lock.lock(); + } + + public void lockInterruptibly() + throws InterruptedException + { + lock.lockInterruptibly(); + } + + public boolean tryLock() { + return lock.tryLock(); + } + + public boolean tryLock(long time,TimeUnit unit) + throws InterruptedException + { + return lock.tryLock(time,unit); + } + + public void unlock() { + lock.unlock(); + } + + public Condition newCondition() { + return lock.newCondition(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/locks/ReadWriteLocker.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,29 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.locks; + + +public interface ReadWriteLocker<T> { + public Locker<T> readLocker(); + public Locker<T> writeLocker(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/locks/TimedLock.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,173 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.locks; + +import java.util.Set; +import java.util.HashSet; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public final class TimedLock extends ProxyLock { + private static final Logger logger = LoggerFactory.getLogger(TimedLock.class); + + private static long timeoutDefault = 10L*60L*1000L; // 10 minutes + private static long waitTimeDefault = 60L*1000L; // 1 minutes + + public static long getTimeoutDefault() { + return timeoutDefault; + } + + public static void setTimeoutDefault(long timeoutDefault) { + TimedLock.timeoutDefault = timeoutDefault; + } + + public static long getWaitTimeDefault() { + return waitTimeDefault; + } + + public static void setWaitTimeDefault(long waitTimeDefault) { + TimedLock.waitTimeDefault = waitTimeDefault; + } + + private static Set<TimedLock> locks = Collections.synchronizedSet(new HashSet<TimedLock>()); + private int count = 0; + private final Object sync = new Object(); + private long whenLocked; + private long whenChecked; + private long timeout = timeoutDefault; + private long waitTime = waitTimeDefault; + private Thread thread = null; + + public TimedLock(Lock lock) { + super(lock); + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public long getWaitTime() { + return waitTime; + } + + public void setWaitTime(long waitTime) { + this.waitTime = waitTime; + } + + private void locked() { + synchronized(sync) { + if( count++ == 0 ) { + whenLocked = System.currentTimeMillis(); + whenChecked = whenLocked; + thread = Thread.currentThread(); + if( !locks.add(this) ) + throw new RuntimeException(); + } + } + } + + public boolean tryLock() { + if( !super.tryLock() ) + return false; + locked(); + return true; + } + + public boolean tryLock(long time,TimeUnit unit) + throws InterruptedException + { + long t = TimeUnit.MILLISECONDS.convert(time,unit); + while( t > waitTime ) { + if( super.tryLock(waitTime,TimeUnit.MILLISECONDS) ) { + locked(); + return true; + } + t -= waitTime; + check(); + } + if( super.tryLock(t,TimeUnit.MILLISECONDS) ) { + locked(); + return true; + } + return false; + } + + public void lockInterruptibly() + throws InterruptedException + { + while(true) { + if( super.tryLock(waitTime,TimeUnit.MILLISECONDS) ) { + locked(); + return; + } + check(); + } + } + + public void lock() { + try { + lockInterruptibly(); + } catch(InterruptedException e) { + throw new RuntimeException(e); + } + } + + public void unlock() { + synchronized(sync) { + if( --count == 0 ) { + if( !locks.remove(this) ) + throw new RuntimeException(); + thread = null; + } + } + super.unlock(); + } + + private void check() { + long now = System.currentTimeMillis(); + long tooOld = now - timeout; + TimedLock[] a = locks.toArray(new TimedLock[0]); + for( int i=0; i<a.length; i++ ) { + TimedLock tl = a[i]; + if( tl.whenChecked < tooOld ) { + StringBuilder buf = new StringBuilder(); + buf.append("lock "+tl+" timed out, locked for "+(System.currentTimeMillis()-tl.whenLocked)+" millis"); + buf.append("\n"+tl.thread); + for( StackTraceElement ste : tl.thread.getStackTrace() ) { + buf.append("\n\t"+ste); + } + logger.error(buf.toString()); + tl.whenChecked = now; + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/locks/TimedLocker.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,112 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.locks; + +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + + +public final class TimedLocker<T> implements Locker<T> { + + private static class Tracker { + final ReentrantLock lock0 = new ReentrantLock(); + final Lock lock = new TimedLock(lock0); + int waiters = 0; + } + + private final Map<T,Tracker> map = new HashMap<T,Tracker>(); + + private Tracker getTracker(T obj) { + Tracker t = map.get(obj); + if( t==null ) { + t = new Tracker(); + map.put(obj,t); + } + return t; + } + + private Tracker getTrackerForWait(T obj) { + synchronized(map) { + Tracker t = getTracker(obj); + t.waiters++; + return t; + } + } + + private void afterWait(Tracker t) { + synchronized(map) { + t.waiters--; + } + } + + private void maybeRemove(T obj,Tracker t) { + if( t.waiters==0 && !t.lock0.isHeldByCurrentThread() ) { + map.remove(obj); + } + } + + public void lock(T obj) { + Tracker t = getTrackerForWait(obj); + try { + t.lock.lock(); + } finally { + afterWait(t); + } + } + + public boolean tryLock(T obj) { + synchronized(map) { + return getTracker(obj).lock.tryLock(); + } + } + + public boolean tryLock(T obj,long time,TimeUnit unit) + throws InterruptedException + { + Tracker t = getTrackerForWait(obj); + try { + return t.lock.tryLock(time,unit); + } finally { + afterWait(t); + } + } + + public void unlock(T obj) { + synchronized(map) { + Tracker t = map.get(obj); + t.lock.unlock(); + maybeRemove(obj,t); + } + } + + public boolean hasLock(T obj) { + synchronized(map) { + Tracker t = map.get(obj); + return t!=null && t.lock0.isHeldByCurrentThread(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/locks/TimedReadWriteLock.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,51 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.locks; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +public final class TimedReadWriteLock implements ReadWriteLock { + private final Lock readLock; + private final Lock writeLock; + + public TimedReadWriteLock() { + this(new ReentrantReadWriteLock()); + } + + public TimedReadWriteLock(ReadWriteLock rwLock) { + this.readLock = new TimedLock(rwLock.readLock()); + this.writeLock = new TimedLock(rwLock.writeLock()); + } + + public Lock readLock() { + return readLock; + } + + public Lock writeLock() { + return writeLock; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/locks/TimedReadWriteLocker.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,154 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.locks; + +import java.util.Map; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +public final class TimedReadWriteLocker<T> implements ReadWriteLocker<T> { + + private static class Tracker { + final ReentrantReadWriteLock lock0 = new ReentrantReadWriteLock(); + final ReadWriteLock lock = new TimedReadWriteLock(lock0); + int waiters = 0; + } + + private final Map<T,Tracker> map = new HashMap<T,Tracker>(); + + private Tracker getTracker(T obj) { + Tracker t = map.get(obj); + if( t==null ) { + t = new Tracker(); + map.put(obj,t); + } + return t; + } + + private Tracker getTrackerForWait(T obj) { + synchronized(map) { + Tracker t = getTracker(obj); + t.waiters++; + return t; + } + } + + private void afterWait(Tracker t) { + synchronized(map) { + t.waiters--; + } + } + + private void maybeRemove(T obj,Tracker t) { + if( t.waiters==0 && !t.lock0.isWriteLocked() && t.lock0.getReadLockCount()==0 ) { + map.remove(obj); + } + } + + private final Locker<T> writeLocker = new Locker<T>() { + + public void lock(T obj) { + Tracker t = getTrackerForWait(obj); + try { + t.lock.writeLock().lock(); + } finally { + afterWait(t); + } + } + + public boolean tryLock(T obj) { + synchronized(map) { + return getTracker(obj).lock.writeLock().tryLock(); + } + } + + public boolean tryLock(T obj,long time,TimeUnit unit) + throws InterruptedException + { + Tracker t = getTrackerForWait(obj); + try { + return t.lock.writeLock().tryLock(time,unit); + } finally { + afterWait(t); + } + } + + public void unlock(T obj) { + synchronized(map) { + Tracker t = map.get(obj); + t.lock.writeLock().unlock(); + maybeRemove(obj,t); + } + } + }; + + private final Locker<T> readLocker = new Locker<T>() { + + public void lock(T obj) { + Tracker t = getTrackerForWait(obj); + try { + t.lock.readLock().lock(); + } finally { + afterWait(t); + } + } + + public boolean tryLock(T obj) { + synchronized(map) { + return getTracker(obj).lock.readLock().tryLock(); + } + } + + public boolean tryLock(T obj,long time,TimeUnit unit) + throws InterruptedException + { + Tracker t = getTrackerForWait(obj); + try { + return t.lock.readLock().tryLock(time,unit); + } finally { + afterWait(t); + } + } + + public void unlock(T obj) { + synchronized(map) { + Tracker t = map.get(obj); + t.lock.readLock().unlock(); + maybeRemove(obj,t); + } + } + }; + + public Locker<T> readLocker() { + return readLocker; + } + + public Locker<T> writeLocker() { + return writeLocker; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/log4j/AppSpecificAppender.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,74 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.log4j; + +import java.util.Map; +import java.util.HashMap; +import org.apache.log4j.Appender; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.ThrowableInformation; + + +public final class AppSpecificAppender extends AppenderSkeleton { + private static final ThreadLocal<String> localApp = new ThreadLocal<String>(); + + public static void setApp(String app) { + localApp.set(app); + } + + private final Map<String,Appender> map = new HashMap<String,Appender>(); + private final Appender defaultAppender; + + public AppSpecificAppender(Appender defaultAppender) { + this.defaultAppender = defaultAppender; + } + + public void add(String app,Appender appender) { + map.put(app,appender); + } + + public boolean requiresLayout() { + return false; + } + + private Appender getAppender() { + String app = (String)localApp.get(); + if( app==null ) + return defaultAppender; + Appender appender = (Appender)map.get(app); + return appender!=null ? appender : defaultAppender; + } + + protected void append(LoggingEvent event) { + getAppender().doAppend(event); + } + + public void close() { + Appender[] appenders = (Appender[])map.values().toArray(new Appender[0]); + for( int i=0; i<appenders.length; i++ ) { + appenders[i].close(); + } + defaultAppender.close(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/log4j/CountingEvaluator.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,23 @@ +package fschmidt.util.log4j; + +import org.apache.log4j.spi.LoggingEvent; +import org.apache.log4j.spi.TriggeringEventEvaluator; + + +public final class CountingEvaluator implements TriggeringEventEvaluator { + private final int limit; + private int count = 0; + + public CountingEvaluator(int limit) { + this.limit = limit; + } + + public boolean isTriggeringEvent(LoggingEvent event) { + if( ++count < limit ) { + return false; + } else { + count = 0; + return true; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/log4j/MultiAppender.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,52 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.log4j; + +import org.apache.log4j.Appender; +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.spi.LoggingEvent; + + +public final class MultiAppender extends AppenderSkeleton { + private final Appender[] a; + + public MultiAppender(Appender[] a) { + this.a = a; + } + + public boolean requiresLayout() { + return false; + } + + protected void append(LoggingEvent event) { + for( int i=0; i<a.length; i++ ) { + a[i].doAppend(event); + } + } + + public void close() { + for( int i=0; i<a.length; i++ ) { + a[i].close(); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/AlternativeMultipartContent.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public final class AlternativeMultipartContent extends MultipartContent { + + public AlternativeMultipartContent(Content[] parts) { + super("alternative",parts); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/Content.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,29 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public interface Content { + public String getType(); + public String getSubtype(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/FileAttachmentContent.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,52 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + +import java.io.InputStream; + +public abstract class FileAttachmentContent implements Content { + private final String type; + private final String subtype; + + public FileAttachmentContent(String type, String subtype) { + this.type = type; + this.subtype = subtype; + } + + public String getType() { + return type; + } + + public String getSubtype() { + return subtype; + } + + public abstract String getFileName(); + + public abstract int getSize(); + + public abstract InputStream getInputStream(); + + public abstract String getContentID(); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/HtmlTextContent.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public final class HtmlTextContent extends TextContent { + + public HtmlTextContent(String text) { + super("html",text); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/InServer.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,28 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public interface InServer { + public MailIterator getMail() throws MailException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/Mail.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,65 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + +import java.util.Date; + + +public interface Mail extends Content { + + public Content getContent() throws MailException; + public void setContent(Content content) throws MailException; + + public String getSubject() throws MailException; + public void setSubject(String subject) throws MailException; + + public MailAddress getFrom() throws MailException; + public void setFrom(MailAddress address) throws MailException; + + public MailAddress getSender() throws MailException; + public void setSender(MailAddress address) throws MailException; + + public MailAddress[] getReplyTo() throws MailException; + public void setReplyTo(MailAddress... addresses) throws MailException; + + public MailAddress[] getTo() throws MailException; + public void setTo(MailAddress... addresses) throws MailException; + + public MailAddress[] getCc() throws MailException; + public void setCc(MailAddress... addresses) throws MailException; + + public MailAddress[] getBcc() throws MailException; + public void setBcc(MailAddress... addresses) throws MailException; + + public String[] getHeader(String name) throws MailException; + public void setHeader(String name,String... values) throws MailException; + public void setHeader(String name,MailAddress address) throws MailException; + + public String getMessageID() throws MailException; + public void setMessageID(String messageID) throws MailException; + + public Date getSentDate() throws MailException; + public void setSentDate(Date sentDate) throws MailException; + + public String getRawInput() throws MailException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MailAddress.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,112 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + +import java.util.regex.Pattern; + +public class MailAddress { + + public static final Pattern EMAIL_PATTERN = + Pattern.compile("\\b(\\w[-+~.\\w]*)@[-\\w]+(\\.[-\\w]+)*\\.[a-zA-Z]+\\b"); + + private final String addrSpec; + private final String displayName; + + public MailAddress(String addrSpec) { + this(addrSpec,null); + } + + public MailAddress(String addrSpec,String displayName) { + this.addrSpec = addrSpec; + this.displayName = displayName; + } + + public String getAddrSpec() { + return addrSpec; + } + + public String getDisplayName() { + return displayName; + } + + public String toString() { + return displayName==null ? addrSpec : displayName+" <"+addrSpec+">"; + } + + public boolean equals(Object o) { + if( !(o instanceof MailAddress) ) + return false; + MailAddress ma = (MailAddress)o; + return addrSpec.equals(ma.addrSpec); + } + + public int hashCode() { + return addrSpec.hashCode(); + } + + public boolean isValid() { + return EMAIL_PATTERN.matcher(addrSpec).matches() && addrSpec.indexOf("..") == -1; + } + + // Test Code ---------------------------------------------------------------------------------- + + public static void main(String[] args) { + String[] valid = { + "none@none.net", + "none-owner@none.net", + "none+owner@none.net", + "none-the-owner@none.net.us", + "none.owner@none.net.us", + "fwd+lists~2B1264539414801-166331~40in.nabble.com+pharo-project-request~40lists.gforge.inria.fr+-679136591@in.nabble.com" + }; + + String[] invalid = { +// "none-@none.net", // not sure + "none...@none.net", + "none@none.net-", + "...none@none.net.us...", + ".-.@---", + "none@none+plus.com", + "none @none.net", + "none@ none.net", + "abc.example.com", +// "abc.@example.com", // not sure + "abc..123@example.com", + "a@b@c@example.com", + "()[]\\;:,<>@example.com" + }; + + System.out.println("--- Valid (Only TRUE)"); + printEmailValidation(valid); + + System.out.println("--- Invalid (Only FALSE)"); + printEmailValidation(invalid); + } + + private static void printEmailValidation(String[] valid) { + for (String s : valid) { + MailAddress mailAddress = new MailAddress(s); + System.out.println(s + " (" + mailAddress.isValid() + ')'); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MailAddressException.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,35 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public class MailAddressException extends MailException { + + public MailAddressException(String msg,Throwable e) { + super(msg,e); + } + + public MailAddressException(String msg) { + super(msg); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MailEncodingException.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,32 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public class MailEncodingException extends MailException { + + public MailEncodingException(String msg,Throwable e) { + super(msg,e); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MailException.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,40 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public class MailException extends RuntimeException { + + public MailException(Throwable e) { + super(e); + } + + public MailException(String msg,Throwable e) { + super(msg,e); + } + + public MailException(String msg) { + super(msg); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MailFactory.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,34 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public interface MailFactory { + public Mail newMail(); + public Mail newMail(String rawInput); + public SmtpServer getSmtpServer(String machineName); + public SmtpServer getSmtpServer(String machineName,String username,String password); + public Pop3Server getPop3Server(String machineName,String username,String password); + public MailAddress parseAddress(String addr) throws MailException; + public boolean isSendingMail(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MailHome.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,103 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +// because javamail sucks + +public final class MailHome { + private MailHome() {} // never + + private static MailFactory mailFactory = new fschmidt.util.mail.javamail.MailFactoryImpl(); + private static SmtpServer defaultSmtpServer = null; + + public static void setMailFactory(MailFactory mailFactory) { + MailHome.mailFactory = mailFactory; + } + + public static SmtpServer getSmtpServer(String machineName) { + return mailFactory.getSmtpServer(machineName); + } + + public static SmtpServer getSmtpServer(String machineName,String username,String password) { + return mailFactory.getSmtpServer(machineName,username,password); + } + + public static SmtpServer getSmtpServerPool(String[] machineNames) { + SmtpServer[] servers = new SmtpServer[machineNames.length]; + for (int i=0; i<servers.length; i++) { + servers[i] = mailFactory.getSmtpServer(machineNames[i]); + } + return new SmtpServerPool(servers); + } + + public static SmtpServer getSmtpServerPool(String[] machineNames,String username,String password) { + SmtpServer[] servers = new SmtpServer[machineNames.length]; + for (int i=0; i<servers.length; i++) { + servers[i] = mailFactory.getSmtpServer(machineNames[i],username,password); + } + return new SmtpServerPool(servers); + } + + public static Pop3Server getPop3Server(String machineName,String username,String password) { + return mailFactory.getPop3Server(machineName,username,password); + } + + public static Pop3Server getPop3ServerPool(String[] machineNames,String username,String password) { + Pop3Server[] servers = new Pop3Server[machineNames.length]; + for (int i=0; i<servers.length; i++) { + servers[i] = mailFactory.getPop3Server(machineNames[i],username,password); + } + return new Pop3ServerPool(servers); + } + + public synchronized static void setDefaultSmtpServer(SmtpServer smtpServer) { + MailHome.defaultSmtpServer = smtpServer; + } + + public synchronized static SmtpServer getDefaultSmtpServer() { + if( defaultSmtpServer==null ) { + String server = System.getProperty("mail.smtp.host"); + if( server==null ) + return null; + defaultSmtpServer = getSmtpServer(server); + } + return defaultSmtpServer; + } + + public static Mail newMail() { + return mailFactory.newMail(); + } + + public static Mail newMail(String rawInput) { + return mailFactory.newMail(rawInput); + } + + public static MailAddress parseAddress(String addr) throws MailException { + return mailFactory.parseAddress(addr); + } + + public static boolean isSendingMail() { + return mailFactory.isSendingMail(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MailIterator.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,32 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + +import java.util.NoSuchElementException; + + +public interface MailIterator { + public boolean hasNext() throws MailException; + public Mail next() throws MailException, NoSuchElementException; + public void close() throws MailException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MailParseException.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,36 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public class MailParseException extends MailException { + + public MailParseException(String msg,Throwable e) { + super(msg,e); + } + + public MailParseException(String msg) { + super(msg); + } + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MixedMultipartContent.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public final class MixedMultipartContent extends MultipartContent { + + public MixedMultipartContent(Content[] parts) { + super("mixed",parts); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/MultipartContent.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,56 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public class MultipartContent implements Content { + private final String subtype; + private final Content[] parts; + + public MultipartContent(String subtype,Content[] parts) { + this.subtype = subtype; + this.parts = parts; + } + + public String getType() { + return "multipart"; + } + + public String getSubtype() { + return subtype; + } + + public Content[] getParts() { + return parts; + } + + public String toString() { + StringBuilder buf = new StringBuilder(); + for( int i=0; i<parts.length; i++ ) { + buf.append( getType() + "/" + getSubtype()+ " - part " + (i+1) + ":\n" ); + buf.append(parts[i]); + buf.append("\n"); + } + return buf.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/PlainTextContent.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,31 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public final class PlainTextContent extends TextContent { + + public PlainTextContent(String text) { + super("plain",text); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/Pop3Server.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,32 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public interface Pop3Server extends InServer { + public void setLeaveOnServer(boolean b); + public void setMailLimit(int maxMailCount); + public String getUsername(); + public void useSsl(); + public void setDebug(boolean b); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/Pop3ServerPool.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,77 @@ +package fschmidt.util.mail; + +// not thread safe +public class Pop3ServerPool implements Pop3Server { + private Pop3Server[] servers; + private int current = 0; + + Pop3ServerPool(Pop3Server... servers) { + this.servers = servers; + setMailLimit(100); + } + + public void setLeaveOnServer(boolean b) { + for (Pop3Server server : servers) { + server.setLeaveOnServer(b); + } + } + + public void setMailLimit(int maxMailCount) { + for (Pop3Server server : servers) { + server.setMailLimit(maxMailCount); + } + } + + // should be same for all + public String getUsername() { + return servers[current].getUsername(); + } + + public void useSsl() { + for (Pop3Server server : servers) { + server.useSsl(); + } + } + + public void setDebug(boolean b) { + for (Pop3Server server : servers) { + server.setDebug(b); + } + } + + public MailIterator getMail() throws MailException { + MailException ex = null; + for (int failed = 0; failed < servers.length; failed++) { + try { + return mailIterator(servers[current].getMail()); + } catch (MailException e) { + ex = e; + nextServer(); + } + } + throw new MailException("all pop3 servers failed", ex); + } + + private MailIterator mailIterator(final MailIterator itr) { + return new MailIterator() { + public boolean hasNext() throws MailException { + return itr.hasNext(); + } + public Mail next() throws MailException { + return itr.next(); + } + public void close() throws MailException { + try { + itr.close(); + } finally { + nextServer(); // always switch to next server + } + } + }; + } + + private void nextServer() { + current = (current < servers.length - 1) ? current+1 : 0; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/SmtpServer.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,35 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public interface SmtpServer { + public void send(Mail mail,MailAddress... addresses) throws MailException; + public void sendFrom(Mail mail,String smtpFrom) throws MailException; + public void useSsl(); + public void useStartTls(); + public void setPort(int port); +// public void setSmtpFrom(String smtpFrom); + public void setDebug(boolean b); + public String getUserName(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/SmtpServerPool.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,92 @@ +package fschmidt.util.mail; + + +public class SmtpServerPool implements SmtpServer { + private SmtpServer[] servers; + private volatile SmtpServer defaultServer; + + SmtpServerPool(SmtpServer... servers) { + this.servers = servers; + defaultServer = servers[0]; + } + + public void send(Mail mail, MailAddress... addresses) throws MailException { + SmtpServer server = defaultServer; + try { + server.send(mail, addresses); + return; + } catch (MailParseException e) { + throw e; + } catch (MailAddressException e) { + throw e; + } catch (MailEncodingException e) { + throw e; + } catch (MailException e) { + for (SmtpServer fallbackServer : servers) { + if (fallbackServer == server) continue; + try { + fallbackServer.send(mail, addresses); + defaultServer = fallbackServer; + return; + } catch (MailException e2) { + e = e2; + } + } + throw new MailException("all smtp servers failed", e); + } + } + + public void sendFrom(final Mail mail, final String smtpFrom) throws MailException { + SmtpServer server = defaultServer; + try { + server.sendFrom(mail, smtpFrom); + return; + } catch (MailParseException e) { + throw e; + } catch (MailAddressException e) { + throw e; + } catch (MailEncodingException e) { + throw e; + } catch (MailException e) { + for (SmtpServer fallbackServer : servers) { + if (fallbackServer == server) continue; + try { + fallbackServer.sendFrom(mail, smtpFrom); + defaultServer = fallbackServer; + return; + } catch (MailException e2) { + e = e2; + } + } + throw new MailException("all smtp servers failed", e); + } + } + + public void useSsl() { + for (SmtpServer server : servers) { + server.useSsl(); + } + } + + public void useStartTls() { + for (SmtpServer server : servers) { + server.useStartTls(); + } + } + + public void setPort(int port) { + for (SmtpServer server : servers) { + server.setPort(port); + } + } + + public void setDebug(boolean b) { + for (SmtpServer server : servers) { + server.setDebug(b); + } + } + + public String getUserName() { + return defaultServer.getUserName(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/TextAttachmentContent.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,37 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public final class TextAttachmentContent extends TextContent { + private final String filename; + + public TextAttachmentContent(String subtype, String text, String filename) { + super(subtype,text); + this.filename = filename; + } + + public String getFilename() { + return filename; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/TextContent.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,50 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public class TextContent implements Content { + private final String subtype; + private final String text; + + public TextContent(String subtype,String text) { + this.subtype = subtype; + this.text = text; + } + + public String getType() { + return "text"; + } + + public String getSubtype() { + return subtype; + } + + public String getText() { + return text; + } + + public String toString() { + return getText(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/UnsupportedContent.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,48 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail; + + +public class UnsupportedContent implements Content { + private final String type; + private final String subtype; + private final Object data; + + public UnsupportedContent(Object data,String type,String subtype) { + this.data = data; + this.type = type; + this.subtype = subtype; + } + + public String getType() { + return type; + } + + public String getSubtype() { + return subtype; + } + + public String toString() { + return data.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/javamail/FileAttachmentContentImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,76 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail.javamail; + +import java.io.IOException; +import java.io.InputStream; + +import javax.mail.MessagingException; +import javax.mail.internet.MimePart; + +import fschmidt.util.mail.FileAttachmentContent; +import fschmidt.util.mail.MailException; + +public final class FileAttachmentContentImpl extends FileAttachmentContent { + private MimePart part; + + FileAttachmentContentImpl(MimePart part, String type, String subtype) { + super(type,subtype); + this.part = part; + } + + public String getFileName() { + try { + return part.getFileName(); + } catch (MessagingException e) { + throw new MailException(e); + } + } + + public int getSize() { + try { + return part.getSize(); + } catch (MessagingException e) { + throw new MailException(e); + } + } + + public InputStream getInputStream() { + try { + return part.getInputStream(); + } catch (MessagingException e) { + throw new MailException(e); + } catch (IOException e) { + throw new MailException(e); + } + } + + public String getContentID() { + try { + return part.getContentID(); + } catch (MessagingException e) { + throw new MailException(e); + } + } + +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/javamail/MailFactoryImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,79 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail.javamail; + +import javax.mail.MessagingException; +import javax.mail.internet.InternetAddress; +import fschmidt.util.mail.MailFactory; +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.SmtpServer; +import fschmidt.util.mail.Pop3Server; +import fschmidt.util.mail.MailAddress; +import fschmidt.util.mail.MailException; + + +public class MailFactoryImpl implements MailFactory { + + static { + System.setProperty("mail.mime.charset", "UTF-8"); + } + + public Mail newMail() { + return new MailImpl(); + } + + public Mail newMail(String rawInput) { + return new MailImpl(rawInput); + } + + public SmtpServer getSmtpServer(String machineName) { + return new SmtpServerImpl(machineName); + } + + public SmtpServer getSmtpServer(String machineName,String username,String password) { + return new SmtpServerImpl(machineName,username,password); + } + + public Pop3Server getPop3Server(String machineName,String username,String password) { + return new Pop3ServerImpl(machineName,username,password); + } + + public MailAddress parseAddress(String addr) throws MailException { + return doParseAddress(addr); + } + + public static MailAddress doParseAddress(String addr) throws MailException { + try { + InternetAddress[] a = InternetAddress.parse(addr); + if (a.length != 1) + throw new MailException("Illegal address: " + addr); + return MailImpl.addr(a[0]); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public boolean isSendingMail() { + return true; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/javamail/MailImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,618 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail.javamail; + +import fschmidt.util.java.IoUtils; +import fschmidt.util.mail.AlternativeMultipartContent; +import fschmidt.util.mail.Content; +import fschmidt.util.mail.FileAttachmentContent; +import fschmidt.util.mail.HtmlTextContent; +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.MailAddress; +import fschmidt.util.mail.MailAddressException; +import fschmidt.util.mail.MailEncodingException; +import fschmidt.util.mail.MailException; +import fschmidt.util.mail.MailParseException; +import fschmidt.util.mail.MixedMultipartContent; +import fschmidt.util.mail.MultipartContent; +import fschmidt.util.mail.PlainTextContent; +import fschmidt.util.mail.TextAttachmentContent; +import fschmidt.util.mail.TextContent; +import fschmidt.util.mail.UnsupportedContent; + +import javax.activation.CommandMap; +import javax.activation.MailcapCommandMap; +import javax.mail.Address; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Part; +import javax.mail.Session; +import javax.mail.internet.AddressException; +import javax.mail.internet.ContentType; +import javax.mail.internet.HeaderTokenizer; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.mail.internet.MimePart; +import javax.mail.internet.MimeUtility; +import javax.mail.internet.ParseException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.Enumeration; + + +public class MailImpl implements Mail { + private static final Session nullSession = null; + + class MyMimeMessage extends MimeMessage { + MyMimeMessage() { + super(nullSession); + } + + MyMimeMessage(InputStream is) throws MessagingException { + super(nullSession,is); + } + + MyMimeMessage(MimeMessage msg) throws MessagingException { + super(msg); + } + + void setSession(Session session) { + this.session = session; + } + + protected void updateHeaders() + throws MessagingException + { + super.updateHeaders(); + if( messageID != null ) + setHeader( "Message-ID", messageID ); + } + } + + final MyMimeMessage msg; + private String messageID = null; + + public MailImpl() { + this.msg = new MyMimeMessage(); + } + + public MailImpl(String rawInput) { + try { + this.msg = new MyMimeMessage(new ByteArrayInputStream(rawInput.getBytes("ISO-8859-1"))); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + MailImpl(MimeMessage msg) throws MessagingException { + this.msg = new MyMimeMessage(msg); + } + + public String getType() { + return "message"; + } + + public String getSubtype() { + return "rfc822"; + } + + public Content getContent() throws MailException { + try { + return getPart(msg); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(IOException e) { + throw MailImpl.e(e); + } + } + + private static Content getPart(Part part) throws MessagingException, IOException { + String ct = part.getContentType().toLowerCase(); + int end = ct.indexOf(';'); + if( end != -1 ) + ct = ct.substring(0,end); + String[] a = ct.split("/"); + if (a.length==0) + return new UnsupportedContent(part.getInputStream(), null, null); + String type = a[0]; + String subtype = a.length>1 ? a[1]:""; + if( type.equals("text") ) { + String text = null; + Object obj; + try { + obj = part.getContent(); + } catch (UnsupportedEncodingException e) { + obj = IoUtils.readAll(new InputStreamReader(part.getInputStream(), "ISO-8859-1")); + if (!isAllAscii((String)obj)) + return new UnsupportedContent(obj, type, subtype); + } + if( Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition()) ) + return new FileAttachmentContentImpl((MimePart)part, type, subtype); + if( obj instanceof String ) { + text = (String)obj; + } else if( obj instanceof InputStream ) { + try { + String charset = null; + String filename = null; + try { + ContentType contentType = new ContentType(part.getContentType()); + charset = MimeUtility.javaCharset(contentType.getParameter("charset")); + filename = contentType.getParameter("name"); + } catch (ParseException e) {} + text = IoUtils.readAll(new InputStreamReader((InputStream)obj,charset!=null?charset:"ISO-8859-1")); + } catch (IOException e) { + return new UnsupportedContent(part.getInputStream(), type, subtype); + } catch (MessagingException e) { + return new UnsupportedContent(part.getInputStream(), type, subtype); + } + } else { + return new FileAttachmentContentImpl((MimePart)part, type, subtype); + } + if( subtype.equals("plain") || subtype.equals("")) + return new PlainTextContent(text); + if( subtype.equals("html") ) + return new HtmlTextContent(text); + return new TextContent(subtype,text); + } + if( type.equals("multipart") && part.getContent() instanceof MimeMultipart) { + Content[] parts = getParts( (MimeMultipart)part.getContent() ); + if( subtype.equals("alternative") ) + return new AlternativeMultipartContent(parts); + if( subtype.equals("mixed") ) + return new MixedMultipartContent(parts); + if( subtype.equals("signed") ) + return new MixedMultipartContent(parts); + return new MultipartContent(subtype,parts); + } + if( type.equals("message") && subtype.equals("rfc822") && part.getContent() instanceof MimeMessage) { + MimeMessage mm = (MimeMessage)part.getContent(); + return new MailImpl(mm); + } + /* + if( type.equals("application") ) { + if( subtype.equals("pgp-signature") ) + return new PlainTextContent((String)obj); + } + */ + return new FileAttachmentContentImpl((MimePart)part, type, subtype); + } + + private static Content[] getParts(MimeMultipart mp) throws MessagingException, IOException { + Content[] parts = new Content[mp.getCount()]; + for( int i=0; i<parts.length; i++ ) { + parts[i] = getPart(mp.getBodyPart(i)); + } + return parts; + } + + private static boolean isAllAscii(String s) { + for (int i=0;i<s.length();i++) + if (nonascii((int)s.charAt(i))) return false; + return true; + } + + // copied from javamail + private static boolean nonascii(int b) { + return b >= 0177 || (b < 040 && b != '\r' && b != '\n' && b != '\t'); + } + + public void setContent(Content content) throws MailException { + try { + setPart(msg,content); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + private final static String defaultCharset = MimeUtility.quote( + MimeUtility.mimeCharset(MimeUtility.getDefaultJavaCharset()), + HeaderTokenizer.MIME); + + private static void setPart(Part part,Content content) throws MessagingException { + if( content instanceof PlainTextContent ) { + TextContent textContent = (TextContent)content; + // Using part.setContent breaks the encoding of non-ascii email + // because com.sun.mail.handlers.text_plain assumes us-ascii charset + // if charset is not specified. Part.setText handles this correctly. + part.setText( textContent.getText() ); + return; + } + if( content instanceof TextAttachmentContent ) { + TextAttachmentContent textContent = (TextAttachmentContent)content; + // Use same logic as javamail MimeBodyPart.setText() to determine charset + String charset = isAllAscii(textContent.getText())?"us-ascii":defaultCharset; + part.setContent( textContent.getText(), content.getType()+'/'+content.getSubtype()+"; charset="+charset); + part.setFileName( textContent.getFilename() ); + return; + } + if( content instanceof FileAttachmentContent ) { + FileAttachmentContent fileContent = (FileAttachmentContent)content; + byte[] contents = new byte[0]; + InputStream is = fileContent.getInputStream(); + try { + contents = IoUtils.readAll(is); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + is.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + part.setContent( contents, content.getType()+'/'+content.getSubtype()); + part.setFileName( fileContent.getFileName() ); + return; + } + if( content instanceof TextContent ) { + TextContent textContent = (TextContent)content; + // Use same logic as javamail MimeBodyPart.setText() to determine charset + String charset = isAllAscii(textContent.getText())?"us-ascii":defaultCharset; + part.setContent( textContent.getText(), content.getType()+'/'+content.getSubtype()+"; charset="+charset); + return; + } + if( content instanceof MultipartContent ) { + part.setContent( makeMimeMultipart((MultipartContent)content) ); + return; + } + if( content instanceof MailImpl ) { + MailImpl mail = (MailImpl)content; + part.setContent( mail.msg, "message/rfc822" ); + part.setDisposition(Part.ATTACHMENT); + return; + } + throw new UnsupportedOperationException("content class "+content.getClass()); + } + + private static MimeMultipart makeMimeMultipart(MultipartContent mc) throws MessagingException { + MimeMultipart mp = new MimeMultipart(mc.getSubtype()); + Content[] parts = mc.getParts(); + for( int i=0; i<parts.length; i++ ) { + MimeBodyPart mbp = new MimeBodyPart(); + setPart(mbp,parts[i]); + mp.addBodyPart(mbp); + } + return mp; + } + + public String getSubject() throws MailException { + try { + return msg.getSubject(); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setSubject(String subject) throws MailException { + try { + msg.setSubject(subject); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + static InternetAddress addr(MailAddress addr) + throws MessagingException, UnsupportedEncodingException + { + return addr.getDisplayName() == null + ? new InternetAddress(addr.getAddrSpec()) + : new InternetAddress(addr.getAddrSpec(),addr.getDisplayName()) + ; + } + + static InternetAddress[] addr(MailAddress[] addrs) + throws MessagingException, UnsupportedEncodingException + { + InternetAddress[] a = new InternetAddress[addrs.length]; + for( int i=0; i<addrs.length; i++ ) { + a[i] = addr(addrs[i]); + } + return a; + } + + static MailAddress addr(Address a) { + if( a==null ) + return null; + InternetAddress addr = (InternetAddress)a; + return addr.getPersonal() == null + ? new MailAddress(addr.getAddress()) + : new MailAddress(addr.getAddress(),addr.getPersonal()) + ; + } + + private static MailAddress[] addr(Address[] a) { + if( a==null ) + return new MailAddress[0]; + MailAddress[] ma = new MailAddress[a.length]; + for( int i=0; i<a.length; i++ ) { + ma[i] = addr(a[i]); + } + return ma; + } + + public MailAddress getFrom() throws MailException { + try { + Address[] addrs = msg.getFrom(); + if( addrs==null || addrs.length==0 ) + throw new MailAddressException("number of from addresses is 0"); + //if( addrs.length > 1 ) + // log.warn("number of from addresses is "+addrs.length); + return addr(addrs[0]); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setFrom(MailAddress address) throws MailException { + try { + msg.setFrom(addr(address)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public MailAddress getSender() throws MailException { + try { + return addr(msg.getSender()); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setSender(MailAddress address) throws MailException { + try { + msg.setSender(addr(address)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public MailAddress[] getReplyTo() throws MailException { + try { + String replyTo = msg.getHeader("Reply-To", ","); + return (replyTo == null) ? null : addr(InternetAddress.parse(replyTo)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setReplyTo(MailAddress... addresses) throws MailException { + try { + msg.setReplyTo(addr(addresses)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public MailAddress[] getTo() throws MailException { + try { + return addr(msg.getRecipients(Message.RecipientType.TO)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public MailAddress[] getCc() throws MailException { + try { + return addr(msg.getRecipients(Message.RecipientType.CC)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public MailAddress[] getBcc() throws MailException { + try { + return addr(msg.getRecipients(Message.RecipientType.BCC)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setTo(MailAddress... addresses) throws MailException { + try { + msg.setRecipients(Message.RecipientType.TO,addr(addresses)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public void setCc(MailAddress... addresses) throws MailException { + try { + msg.setRecipients(Message.RecipientType.CC,addr(addresses)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public void setBcc(MailAddress... addresses) throws MailException { + try { + msg.setRecipients(Message.RecipientType.BCC,addr(addresses)); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public String[] getHeader(String name) throws MailException { + try { + return msg.getHeader(name); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setHeader(String name,String... values) throws MailException { + try { + if( values==null ) { + msg.removeHeader(name); + } else { + msg.setHeader(name,values[0]); + for( int i=1; i<values.length; i++ ) { + msg.addHeader(name,values[i]); + } + } + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setHeader(String name, MailAddress address) throws MailException { + try { + msg.setHeader(name, addr(address).toString()); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public String getMessageID() throws MailException { + try { + if( messageID != null ) + return messageID; + return msg.getMessageID(); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setMessageID(String messageID) throws MailException { + this.messageID = messageID; + } + + public Date getSentDate() throws MailException { + try { + return msg.getSentDate(); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public void setSentDate(Date sentDate) throws MailException { + try { + msg.setSentDate(sentDate); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + + public String getRawInput() throws MailException { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + msg.writeTo(out); + return out.toString("ISO-8859-1"); + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(IOException e) { + throw new MailException(e); + } + } + + public String toString() { + try { + StringBuilder buf = new StringBuilder(); + for( Enumeration en=msg.getAllHeaderLines(); en.hasMoreElements(); ) { + String header = (String)en.nextElement(); + buf.append(header).append("\n"); + } + buf.append("\n"); + buf.append(getContent()); + return buf.toString(); + } catch(MessagingException e) { + throw new RuntimeException(e); + } + } + + + // exception handling + + static MailException e(MessagingException e) { + return e(e.getMessage(),e); + } + + static MailException e(String msg,AddressException e) { + return new MailAddressException(msg,e); + } + + static MailException e(String msg,MessagingException e) { + if( e instanceof AddressException ) + return e(msg,(AddressException)e); + else if(e instanceof ParseException ) + return e(msg,(ParseException)e); + if (msg!=null && msg.startsWith("Missing start boundary")) + return new MailParseException(msg,e); + Exception e2 = e.getNextException(); + return e2==null ? new MailException(msg,e) : e(msg,e2); + } + + static MailException e(String msg,Exception e) { + if( e instanceof MessagingException ) + return e(msg,(MessagingException)e); + return new MailException(msg,e); + } + + static MailException e(String msg,ParseException e) { + return new MailParseException(msg,e); + } + + static MailException e(IOException e) { + return e(e.getMessage(),e); + } + + static MailException e(String msg,IOException e) { + if ( e instanceof UnsupportedEncodingException ) + return e(msg,(UnsupportedEncodingException)e); + if (msg!=null && msg.startsWith("Unknown encoding")) + return new MailEncodingException(msg,e); + return new MailException(msg,e); + } + + static MailException e(String msg,UnsupportedEncodingException e) { + return new MailEncodingException(msg,e); + } + + static { + MailcapCommandMap mcm = (MailcapCommandMap)CommandMap.getDefaultCommandMap(); + mcm.addMailcap("text/x-aol; ; x-java-content-handler=com.sun.mail.handlers.text_plain"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/javamail/MstorInServer.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,130 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail.javamail; + +import java.io.File; +import java.io.IOException; +import java.util.NoSuchElementException; +import java.util.Properties; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.PasswordAuthentication; +import javax.mail.Authenticator; +import javax.mail.Message; +import javax.mail.Folder; +import javax.mail.Store; +import javax.mail.URLName; +import javax.mail.internet.MimeMessage; +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.MailException; +import fschmidt.util.mail.MailIterator; +import fschmidt.util.mail.InServer; + + +public final class MstorInServer implements InServer { + private final Properties props = new Properties(); + private Session session = null; + private final File file; + private Store store = null; + + public MstorInServer(File file) { + this.file = file; + } + + public void setMetaEnabled(boolean b) { + props.setProperty( "mstor.meta.enabled", Boolean.toString(b) ); + } + + private synchronized Session getSession() { + if( session==null ) { + session = Session.getInstance(props); + } + return session; + } + + public synchronized MailIterator getMail() throws MailException { + if( store != null ) { + store = null; + throw new MailException("previous MailIterator not closed"); + } + try { + store = getSession().getStore(new URLName("mstor:"+file.getParentFile().getCanonicalPath())); + store.connect(); + final Folder folder = store.getFolder(file.getName()); + folder.open(Folder.READ_ONLY); + final int nMsgs = folder.getMessageCount(); +//System.out.println("nMsgs="+nMsgs); + return new MailIterator() { + int i = 1; + + public boolean hasNext() { + return i <= nMsgs; + } + + public Mail next() throws MailException, NoSuchElementException { + if( i > nMsgs ) + throw new NoSuchElementException(); +//System.out.println("i="+i); + try { + MimeMessage msg = (MimeMessage)folder.getMessage(i++); + return new MailImpl(msg); + } catch(MessagingException e) { + throw MailImpl.e(toString(),e); + } + } + + public void close() throws MailException { + try { + folder.close(false); + store.close(); + } catch(MessagingException e) { + throw MailImpl.e(toString(),e); + } finally { + store = null; + } + } + }; + } catch(IOException e) { + cleanup(); + throw new MailException(toString(),e); + } catch(MessagingException e) { + cleanup(); + throw MailImpl.e(toString(),e); + } + } + + private void cleanup() { + if( store != null ) { + try { + store.close(); + } catch(MessagingException e2) {} + store = null; + } + } + + public String toString() { + return "MstorInServer("+file+")"; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/javamail/Pop3ServerImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,164 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail.javamail; + +import java.util.NoSuchElementException; +import java.util.Properties; + +import javax.mail.Flags; +import javax.mail.Folder; +import javax.mail.MessageRemovedException; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Store; +import javax.mail.internet.MimeMessage; + +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.MailException; +import fschmidt.util.mail.MailIterator; +import fschmidt.util.mail.Pop3Server; + + +final class Pop3ServerImpl implements Pop3Server { + private final Properties props = new Properties(); + private Session session = null; + private final String host; + private final String username; + private final String password; + private boolean leaveOnServer = false; + private int maxMailCount = Integer.MAX_VALUE; + private Store store = null; + + Pop3ServerImpl(String machineName,String username,String password) { + this.host = machineName; + this.username = username; + this.password = password; + } + + public void setLeaveOnServer(boolean b) { + this.leaveOnServer = b; + } + + public void setMailLimit(int maxMailCount) { + this.maxMailCount = maxMailCount; + } + + public void useSsl() { + props.setProperty( "mail.pop3.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + props.setProperty( "mail.pop3.port", "995"); + props.setProperty( "mail.pop3.socketFactory.port", "995"); + } + + public void setDebug(boolean b) { + props.setProperty("mail.debug", Boolean.toString(b)); + } + + private synchronized Session getSession() { + if( session==null ) { + session = Session.getInstance(props); + } + return session; + } + + public synchronized MailIterator getMail() throws MailException { + if( store != null ) { + store = null; + throw new MailException("previous MailIterator not closed"); + } + try { + store = getSession().getStore("pop3"); + store.connect(host,username,password); + final Folder folder = store.getFolder("INBOX"); + folder.open(Folder.READ_WRITE); + final int nMsgs = Math.min(maxMailCount,folder.getMessageCount()); + return new MailIterator() { + int i = 1; + Mail nextMail = null; + + public boolean hasNext() throws MailException { + //return i <= nMsgs; + while (nextMail==null && i<=nMsgs) { + try { + MimeMessage msg = (MimeMessage)folder.getMessage(i++); + if ( msg.isExpunged() || msg.isSet(Flags.Flag.DELETED) ) { + continue; + } + if ( !leaveOnServer ) { + msg.setFlag(Flags.Flag.DELETED,true); + } + nextMail = new MailImpl(msg); + } catch (MessageRemovedException e) { + // continue + } catch (MessagingException e) { + throw MailImpl.e(toString(),e); + } + } + return nextMail!=null; + } + + public Mail next() throws MailException, NoSuchElementException { + try { + if (nextMail==null && !hasNext()) + throw new NoSuchElementException(); + return nextMail; + } finally { + nextMail=null; + } + } + + public void close() throws MailException { + try { + if( folder.isOpen() ) + folder.close(true); + store.close(); + } catch(MessagingException e) { + throw MailImpl.e(toString(),e); + } finally { + store = null; + } + } + }; + } catch(MessagingException e) { + cleanup(); + throw MailImpl.e(toString(),e); + } + } + + private void cleanup() { + if( store != null ) { + try { + store.close(); + } catch(MessagingException e2) {} + store = null; + } + } + + public String getUsername() { + return username; + } + + public String toString() { + return "Pop3ServerImpl("+host+","+username+","+password+")"; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/javamail/SmtpServerImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,134 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.mail.javamail; + +import java.io.UnsupportedEncodingException; +import java.util.Properties; + +import javax.mail.Address; +import javax.mail.Authenticator; +import javax.mail.MessagingException; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.Transport; + +import com.sun.mail.smtp.SMTPMessage; + +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.MailAddress; +import fschmidt.util.mail.MailException; +import fschmidt.util.mail.SmtpServer; + + +final class SmtpServerImpl implements SmtpServer { + private final PasswordAuthentication pa; + private final Authenticator auth = new Authenticator() { + protected PasswordAuthentication getPasswordAuthentication() { + return pa; + } + }; + private final Properties props = new Properties(System.getProperties()); + private Session session = null; + + SmtpServerImpl(String machineName) { + props.setProperty("mail.smtp.host",machineName); + pa = null; + } + + SmtpServerImpl(String machineName,String username,String password) { + props.setProperty("mail.smtp.host",machineName); + props.setProperty("mail.smtp.auth","true"); + pa = new PasswordAuthentication(username,password); + } + + public String getUserName() { + return pa==null ? null : pa.getUserName(); + } + + public void useSsl() { + props.setProperty("mail.smtp.starttls.enable","true"); + props.setProperty("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory"); + //props.setProperty("mail.smtp.socketFactory.port", "465"); + //props.setProperty("mail.smtp.port", "465"); + } + + public void useStartTls() { + props.setProperty("mail.smtp.starttls.enable","true"); + } + + public void setPort(int port) { + String s = Integer.toString(port); + props.setProperty("mail.smtp.socketFactory.port", s); + props.setProperty("mail.smtp.port", s); + } +/* + public void setSmtpFrom(String smtpFrom) { + props.setProperty("mail.smtp.from", smtpFrom); + } +*/ + public void setDebug(boolean b) { + props.setProperty("mail.debug", Boolean.toString(b)); + } + + private synchronized Session getSession() { + if( session==null ) { + session = Session.getInstance(props,auth); + } + return session; + } + + public void send(Mail m,MailAddress... addresses) throws MailException { + try { + MailImpl.MyMimeMessage msg = ((MailImpl)m).msg; + msg.setSession(getSession()); + if( addresses.length==0 ) { + Transport.send(msg); + } else { + Address[] a = MailImpl.addr(addresses); + Transport.send(msg,a); + } + } catch(MessagingException e) { + throw MailImpl.e(e); + } catch(UnsupportedEncodingException e) { + throw new MailException(e); + } + } + + public void sendFrom(Mail m,String from) throws MailException { + try { + final MailImpl.MyMimeMessage msg = ((MailImpl)m).msg; + msg.setSession(getSession()); + SMTPMessage smtpMsg = new SMTPMessage(msg) { + protected void updateHeaders() throws MessagingException { + super.updateHeaders(); + setHeader("Message-ID", msg.getMessageID()); + } + }; + smtpMsg.setEnvelopeFrom(from); + Transport.send(smtpMsg); + } catch(MessagingException e) { + throw MailImpl.e(e); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/nomail/MailFactoryImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,41 @@ +package fschmidt.util.mail.nomail; + +import fschmidt.util.mail.MailFactory; +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.SmtpServer; +import fschmidt.util.mail.Pop3Server; +import fschmidt.util.mail.MailAddress; +import fschmidt.util.mail.MailException; +import fschmidt.util.mail.javamail.MailImpl; + + +public final class MailFactoryImpl implements MailFactory { + + public Mail newMail() { + return new MailImpl(); + } + + public Mail newMail(String rawInput) { + return new MailImpl(rawInput); + } + + public SmtpServer getSmtpServer(String machineName) { + return new SmtpServerImpl(); + } + + public SmtpServer getSmtpServer(String machineName,String username,String password) { + return new SmtpServerImpl(); + } + + public Pop3Server getPop3Server(String machineName,String username,String password) { + return new Pop3ServerImpl(); + } + + public MailAddress parseAddress(String addr) throws MailException { + return fschmidt.util.mail.javamail.MailFactoryImpl.doParseAddress(addr); + } + + public boolean isSendingMail() { + return false; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/nomail/Pop3ServerImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,30 @@ +package fschmidt.util.mail.nomail; + +import java.util.NoSuchElementException; +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.Pop3Server; +import fschmidt.util.mail.MailIterator; + + +final class Pop3ServerImpl implements Pop3Server { + + public MailIterator getMail() { + return new MailIterator(){ + public boolean hasNext() { + return false; + } + public Mail next() throws NoSuchElementException { + throw new NoSuchElementException(); + } + public void close() {} + }; + } + + public void setLeaveOnServer(boolean b) {} + public void setMailLimit(int maxMailCount) {} + public String getUsername() { + throw new UnsupportedOperationException(); + } + public void useSsl() {} + public void setDebug(boolean b) {} +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/mail/nomail/SmtpServerImpl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,29 @@ +package fschmidt.util.mail.nomail; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import fschmidt.util.mail.Mail; +import fschmidt.util.mail.SmtpServer; +import fschmidt.util.mail.MailAddress; + + +final class SmtpServerImpl implements SmtpServer { + private static final Logger logger = LoggerFactory.getLogger(SmtpServerImpl.class); + + public void send(Mail mail,MailAddress... addresses) { + logger.warn("no mail sent"); + } + + public void sendFrom(Mail mail,String smtpFrom) { + logger.warn("no mail sent"); + } + + public void useSsl() {} + public void useStartTls() {} + public void setPort(int port) {} +// public void setSmtpFrom(String smtpFrom) {} + public void setDebug(boolean b) {} + public String getUserName() { + return null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/AuthenticatingFilter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,68 @@ +/* +Copyright (c) 2009 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +public final class AuthenticatingFilter implements Filter { + private String realm; + private String usernameAndPassword; + + public void init(FilterConfig filterConfig) + throws ServletException + { + realm = filterConfig.getInitParameter("realm"); + String username = filterConfig.getInitParameter("username"); + String password = filterConfig.getInitParameter("password"); + usernameAndPassword = username + ":" + password; + } + + public void destroy() {} + + public void doFilter( + ServletRequest req, + ServletResponse res, + FilterChain chain + ) + throws IOException, ServletException + { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + String auth = ServletUtils.getAuthorization(request); + if( usernameAndPassword.equals(auth) ) { + chain.doFilter(request,response); + } else { + ServletUtils.sendAuthenticate(response,realm); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/AuthorizingServlet.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,34 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +public interface AuthorizingServlet { + public String getAuthorizationKey(HttpServletRequest request) throws ServletException; // return null if no authentication + public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/BadBotFilter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,84 @@ +/* +Copyright (c) 2009 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + + +public final class BadBotFilter implements Filter { + private int max; + private final Map<String,int[]> map = new HashMap<String,int[]>(); + + public void init(FilterConfig filterConfig) + throws ServletException + { + max = Integer.parseInt(filterConfig.getInitParameter("max")); + } + + public void destroy() {} + + public void doFilter( + ServletRequest req, + ServletResponse res, + FilterChain chain + ) + throws IOException, ServletException + { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + String ip = request.getRemoteAddr(); + int[] counter; + synchronized(map) { + counter = map.get(ip); + if( counter!=null ) { + counter[0]++; + } else { + counter = new int[]{1}; + map.put(ip,counter); + } + } + try { + if( counter[0] > max ) { + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "too many active connections"); + } else { + chain.doFilter(request,response); + } + } finally { + synchronized(map) { + if( --counter[0] == 0 ) + map.remove(ip); + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/BetterRequestWrapper.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,53 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import java.util.Enumeration; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import fschmidt.util.java.IteratorEnumeration; + + +public class BetterRequestWrapper extends HttpServletRequestWrapper { + + public BetterRequestWrapper(HttpServletRequest request) { + super(request); + } + + public Enumeration getParameterNames() { + @SuppressWarnings("unchecked") + IteratorEnumeration en = new IteratorEnumeration(getParameterMap().keySet().iterator()); + return en; + } + + public String[] getParameterValues(String name) { + return (String[])getParameterMap().get(name); + } + + public String getParameter(String name) { + String[] a = getParameterValues(name); + return a==null ? null : a[0]; + } + +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/CanonicalUrl.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,8 @@ +package fschmidt.util.servlet; + +import javax.servlet.http.HttpServletRequest; + + +public interface CanonicalUrl { + public String getCanonicalUrl(HttpServletRequest request); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/ConnectionLimitFilter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,119 @@ +/* +Copyright (c) 2009 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import org.eclipse.jetty.continuation.Continuation; +import org.eclipse.jetty.continuation.ContinuationListener; +import org.eclipse.jetty.continuation.ContinuationSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; + + +public class ConnectionLimitFilter implements Filter { + private static final Logger logger = LoggerFactory.getLogger(ConnectionLimitFilter.class); + + public static int max; + public static final AtomicInteger counter = new AtomicInteger(); + public static Queue<Continuation> queue = new ArrayBlockingQueue<Continuation>(1); + public static long timeout = 0L; + + private static final ContinuationListener listener = new ContinuationListener() { + public void onComplete(Continuation continuation) {} + + public void onTimeout(Continuation continuation) { + queue.remove(continuation); + logger.error("Connection timed out"); + HttpServletResponse response = (HttpServletResponse)continuation.getServletResponse(); + try { + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "too many active connections"); + } catch(IOException e) { + logger.info("",e); + } + } + }; + + public void init(FilterConfig filterConfig) + throws ServletException + { + max = Integer.parseInt(filterConfig.getInitParameter("max")); + String queueSize = filterConfig.getInitParameter("queueSize"); + if( queueSize != null ) + queue = new ArrayBlockingQueue<Continuation>( Integer.parseInt(queueSize) ); + String timeoutDelay = filterConfig.getInitParameter("timeoutDelay"); + if( timeoutDelay != null ) + timeout = Long.parseLong(timeoutDelay); + } + + public void destroy() {} + + public void doFilter( + ServletRequest req, + ServletResponse res, + FilterChain chain + ) + throws IOException, ServletException + { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + try { + if( counter.incrementAndGet() <= max ) { + try { + chain.doFilter(request,response); + } finally { + Continuation continuation = queue.poll(); + if( continuation != null && continuation.isSuspended() ) { + continuation.resume(); + } + } + } else { + Continuation continuation = ContinuationSupport.getContinuation(request); + if( timeout > 0L ) + continuation.setTimeout(timeout); + continuation.suspend(); + if( continuation.isInitial() ) + continuation.addContinuationListener(listener); + if( !queue.offer(continuation) ) { + logger.error("Connection rejected: "+ServletUtils.getCurrentURL(request)); + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "too many active connections"); + continuation.complete(); + } + } + } finally { + counter.decrementAndGet(); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/DbCache.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,194 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Date; +import java.util.Map; +import java.util.HashMap; +import fschmidt.db.DbDatabase; +import fschmidt.db.postgres.DbDatabaseImpl; + + +public final class DbCache implements HttpCache { + private final DbDatabase db; + private long lastClear; + private static final String LAST_CLEAR = "_last_clear"; + + public DbCache(DbDatabase db) { + this.db = db; + setLastClear(); + } + + private void setLastClear() { + try { + Connection con = db.getConnection(); + try { + PreparedStatement stmt = con.prepareStatement( + "select last_modified from http_cache where event=?" + ); + stmt.setString(1,LAST_CLEAR); + ResultSet rs = stmt.executeQuery(); + if( !rs.next() ) { + modified(LAST_CLEAR); + stmt.setString(1,LAST_CLEAR); + rs = stmt.executeQuery(); + rs.next(); + } + lastClear = getLastModified(rs); + stmt.close(); + } finally { + con.close(); + } + } catch(SQLException e) { + throw new RuntimeException(e); + } + } + + private static long getLastModified(ResultSet rs) throws SQLException { + return rs.getTimestamp("last_modified").getTime()/1000*1000; + } + + public long[] lastModifieds(final String[] modifyingEvents) { + switch( modifyingEvents.length ) { + case 0: + return new long[0]; + case 1: + try { + Connection con = db.getConnection(); + PreparedStatement stmt = con.prepareStatement( + "select last_modified from http_cache where event=?" + ); + try { + stmt.setString(1,modifyingEvents[0].toString()); + ResultSet rs = stmt.executeQuery(); + return new long[]{ rs.next() ? getLastModified(rs) : lastClear }; + } finally { + stmt.close(); + con.close(); + } + } catch(SQLException e) { + throw new RuntimeException(e); + } + default: + StringBuilder select = new StringBuilder(); + try { + final String[] events = new String[modifyingEvents.length]; + final long[] rtn = new long[modifyingEvents.length]; + for( int i=0; i<modifyingEvents.length; i++ ) { + events[i] = modifyingEvents[i].toString(); + rtn[i] = lastClear; + } + Map<String,Long> map = new HashMap<String,Long>(); + select.append( + "select event,last_modified from http_cache where event in (" + ).append( db.arcana().quote(events[0]) ); + for( int i=1; i<events.length; i++ ) { + select.append( ',' ).append( db.arcana().quote(events[i]) ); + } + select.append( ")" ); + Connection con = db.getConnection(); + try { + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( select.toString() ); + while( rs.next() ) { + map.put( rs.getString("event"), getLastModified(rs) ); + } + stmt.close(); + } finally { + con.close(); + } + for( int i=0; !map.isEmpty(); i++ ) { + Long lm = map.remove(events[i]); + if( lm != null ) + rtn[i] = lm; + } + return rtn; + } catch(SQLException e) { + throw new RuntimeException(select.toString(),e); + } + } + } + + public void modified(final String event) { + db.runAfterCommit(new Runnable(){public void run(){ + try { + Connection con = db.getConnection(); + try { + boolean didUpdate; + { + PreparedStatement stmt = con.prepareStatement( + "update http_cache set last_modified=now() where event = ?" + ); + stmt.setString(1,event); + didUpdate = stmt.executeUpdate() > 0; + stmt.close(); + } + if( !didUpdate ) { + PreparedStatement stmt = con.prepareStatement( + "insert into http_cache (event) values (?)" + ); + stmt.setString(1,event); + DbDatabaseImpl.executeUpdateIgnoringDuplicateKeys(stmt); + stmt.close(); + } + } finally { + con.close(); + } + } catch(SQLException e) { + throw new RuntimeException("event = "+event,e); + } + }}); + } + + public void clear() { + try { + Connection con = db.getConnection(); + try { + Statement stmt = con.createStatement(); + stmt.executeUpdate( + "delete from http_cache" + ); + stmt.close(); + } finally { + con.close(); + } + } catch(SQLException e) { + throw new RuntimeException(e); + } + setLastClear(); + } + +/* +assumes: + +create table http_cache ( + event varchar not null primary key, + last_modified timestamp with time zone NOT NULL DEFAULT now() +); +*/ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/HttpCache.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,33 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + + +public interface HttpCache { + public long[] lastModifieds(String[] modifyingEvents); + + public void modified(String modifyingEvent); + + public void clear(); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/JarDefaultServlet.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,36 @@ +package fschmidt.util.servlet; + +import org.eclipse.jetty.servlet.DefaultServlet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Date; + + +public final class JarDefaultServlet extends DefaultServlet { + private static final Logger logger = LoggerFactory.getLogger(JarDefaultServlet.class); + + private static final long built; + static { + try { + built = new Date(ClassLoader.getSystemResource("fschmidt/util/servlet/JarDefaultServlet.class").openConnection().getLastModified()).getTime()/1000*1000; + } catch(IOException e) { + logger.error("",e); + throw new RuntimeException(e); + } + } + + protected void doGet(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + if( built <= request.getDateHeader("If-Modified-Since") ) { + response.sendError(HttpServletResponse.SC_NOT_MODIFIED); + return; + } + super.doGet(request,response); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/JtpContext.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,45 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + + +public interface JtpContext { + public final String attrName = JtpContext.class.getName(); + + public HttpCache getHttpCache(); + public void setHttpCache(HttpCache httpCache); + public void addCustomHeader(String key, String value); + public void unloadServlets(); + public void setUrlMapper(UrlMapper urlMapper); + + public long getTimeLimit(); + public void setTimeLimit(long timeLimit); + public void setTimeLimit(HttpServletRequest request,long timeLimit); + public void setEtag( HttpServletRequest request, HttpServletResponse response, String... modifyingEvents ); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/JtpContextServlet.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,806 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import fschmidt.util.java.ProcUtils; +import fschmidt.util.java.SimpleClassLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; + + +public final class JtpContextServlet extends HttpServlet implements JtpContext { + private static final Logger logger = LoggerFactory.getLogger(JtpContextServlet.class); + + private static final Set<String> allowedMethods = new HashSet<String>(Arrays.asList( + "GET", "POST", "HEAD" + )); + private String base; + private boolean reload = false; + private boolean recompile = false; + private SimpleClassLoader.Filter filter = null; + private ClassLoader cl = null; + private Map<String,HttpServlet> map = new HashMap<String,HttpServlet>(); + private long clTime; + private Object lock = new Object(); + private HttpCache httpCache; + private boolean isCaching; + private String characterEncoding; + private Map<String, String> customHeaders = new HashMap<String, String>(); + private UrlMapper urlMapper = new UrlMapper() { + public UrlMapping getUrlMapping(HttpServletRequest request) { + return null; + } + }; + private Set<String> errorCache = null; + private Collection<String> ipList = null; + private static final String authKeyAttr = "authKey"; + private static final String[] noModifyingEvents = new String[]{"_"}; + + public void setUrlMapper(UrlMapper urlMapper) { + this.urlMapper = urlMapper; + } + + public HttpCache getHttpCache() { + return httpCache; + } + + public void setHttpCache(HttpCache httpCache) { + this.httpCache = httpCache; + } + + public void addCustomHeader(String key, String value) { + this.customHeaders.put(key, value); + } + + public void unloadServlets() { + if( !reload ) + throw new UnsupportedOperationException("'reload' must be set"); + synchronized(lock) { + cl = new SimpleClassLoader(filter); + map = new HashMap<String,HttpServlet>(); + clTime = System.currentTimeMillis(); + } + } + + public void setBase(String base) { + if( base==null ) + throw new NullPointerException(); + this.base = base; + } + + public void init() + throws ServletException + { + ServletContext context = getServletContext(); + String newBase = getInitParameter("base"); + if( newBase != null ) + setBase(newBase); + recompile = Boolean.valueOf(getInitParameter("recompile")); + reload = recompile || Boolean.valueOf(getInitParameter("reload")); + if( reload ) { + filter = new SimpleClassLoader.Filter(){ + final String s = base + "."; + public boolean load(String className) { + return className.startsWith(s); + } + }; + unloadServlets(); + } + context.setAttribute(JtpContext.attrName,this); + String servletS = getInitParameter("servlet"); + if( servletS != null ) { + throw new RuntimeException("the 'servlet' init parameter is no longer supported"); + } + isCaching = "true".equalsIgnoreCase(getInitParameter("cache")); + if( isCaching ) { + if( httpCache==null ) { + logger.error("can't set init parameter 'cache' to true without httpCache"); + System.exit(-1); + } + logger.info("cache"); + } + characterEncoding = getInitParameter("characterEncoding"); + { + String s = getInitParameter("timeLimit"); + if( s != null ) + timeLimit = Long.parseLong(s); + } + { + String s = getInitParameter("errorCacheSize"); + if( s != null ) { + final int errorCacheSize = Integer.parseInt(s); + errorCache = Collections.synchronizedSet(Collections.newSetFromMap(new LinkedHashMap<String,Boolean>(){ + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > errorCacheSize; + } + })); + } + } + { + String s = getInitParameter("ipListSize"); + if( s != null ) { + final int ipListSize = Integer.parseInt(s); + ipList = Collections.synchronizedList(new LinkedList<String>() { + public boolean add(String s) { + if( contains(s) ) + return false; + super.add(s); + if( size() > ipListSize ) + removeFirst(); + return true; + } + }); + } + } + } + + private boolean isInErrorCache(String s) { + return errorCache==null || !errorCache.add(s); + } + + private boolean isInIpList(String ip) { + return ipList!=null && !ipList.add(ip); + } + + public static interface DestroyListener { + public void destroyed(); + } + + private DestroyListener destroyListener = null; + + public void addDestroyListener(DestroyListener dl) { + synchronized(lock) { + if( destroyListener!=null ) + throw new RuntimeException("only one DestroyListener allowed"); + destroyListener = dl; + } + } + + public void destroy() { + synchronized(lock) { + if( destroyListener != null ) + destroyListener.destroyed(); + } + } + + public static final class RequestAndResponse { + public final HttpServletRequest request; + public final HttpServletResponse response; + + public RequestAndResponse(HttpServletRequest request,HttpServletResponse response) { + this.request = request; + this.response = response; + } + } + + public static interface CustomWrappers { + public RequestAndResponse wrap(HttpServletRequest request, HttpServletResponse response); + } + + private CustomWrappers customWrappers; + + public void setCustomWrappers(CustomWrappers customWrappers) { + this.customWrappers = customWrappers; + } + + private static String hideNull(String s) { + return s==null ? "" : s; + } + + private String getServletPath(HttpServletRequest request) { + return request.getServletPath() + hideNull(request.getPathInfo()); + } + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + final TimeLimit tl = startTimeLimit(request); + response = new HttpServletResponseWrapper(response) { + PrintWriter writer = null; + ServletOutputStream out = null; + + public PrintWriter getWriter() + throws java.io.IOException + { + if( writer==null ) { + writer = new PrintWriter(super.getWriter()) { + public void write(String s,int off,int len) { + long t = System.currentTimeMillis(); + super.write(s,off,len); + tl.ioTime += System.currentTimeMillis() - t; + } + public void write(char[] buf,int off,int len) { + long t = System.currentTimeMillis(); + super.write(buf,off,len); + tl.ioTime += System.currentTimeMillis() - t; + } + public void write(int c) { + long t = System.currentTimeMillis(); + super.write(c); + tl.ioTime += System.currentTimeMillis() - t; + } + public void flush() { + long t = System.currentTimeMillis(); + super.flush(); + tl.ioTime += System.currentTimeMillis() - t; + } + public void println() { + long t = System.currentTimeMillis(); + super.println(); + tl.ioTime += System.currentTimeMillis() - t; + } + }; + } + return writer; + } + + public ServletOutputStream getOutputStream() + throws java.io.IOException + { + if( out==null ) { + final ServletOutputStream sos = super.getOutputStream(); + out = new ServletOutputStream() { + public void write(byte[] b,int off,int len) throws IOException { + long t = System.currentTimeMillis(); + sos.write(b,off,len); + tl.ioTime += System.currentTimeMillis() - t; + } + public void write(byte[] b) throws IOException { + long t = System.currentTimeMillis(); + sos.write(b); + tl.ioTime += System.currentTimeMillis() - t; + } + public void write(int c) throws IOException { + long t = System.currentTimeMillis(); + sos.write(c); + tl.ioTime += System.currentTimeMillis() - t; + } + public void flush() throws IOException { + long t = System.currentTimeMillis(); + sos.flush(); + tl.ioTime += System.currentTimeMillis() - t; + } + }; + } + return out; + } + + public void sendError(int sc) throws IOException { + long t = System.currentTimeMillis(); + super.sendError(sc); + tl.ioTime += System.currentTimeMillis() - t; + } + + public void sendRedirect(String location) throws IOException { + if( containsHeader("Expires") ) + setHeader("Expires",null); + if( containsHeader("Last-Modified") ) + setHeader("Last-Modified",null); + if( containsHeader("Etag") ) + setHeader("Etag",null); + if( containsHeader("Cache-Control") ) + setHeader("Cache-Control",null); + if( containsHeader("Content-Type") ) + setHeader("Content-Type",null); + if( containsHeader("Content-Length") ) +// setHeader("Content-Length",null); + setContentLength(0); + super.sendRedirect(location); + } + }; + service2(request,response); + checkTimeLimit(request); + } + + private void service2(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException + { + if( !allowedMethods.contains(request.getMethod()) ) { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } +// String contextPath = request.getContextPath(); +// String contextUrl = ServletUtils.getContextURL(request); + + // First we set the character encoding because any manipulation + // of request parameters will break without this. + response.setHeader("Content-Type","text/html; charset=utf-8"); // default, servlet can override + if( characterEncoding != null ) { + response.setCharacterEncoding(characterEncoding); + request.setCharacterEncoding(characterEncoding); + } + + HttpServlet servlet; + String path = getServletPath(request); + + UrlMapping urlMapping = urlMapper.getUrlMapping(request); + if( urlMapping != null ) { + try { + servlet = getServletFromClass(urlMapping.servletClass.getName()); + } catch(ClassNotFoundException e) { + throw new RuntimeException(e); + } + final Map params = urlMapping.parameterMap; + request = new BetterRequestWrapper(request) { + public Map getParameterMap() { + return params; + } + }; + } else { + try { + servlet = getServlet(path); + } catch(ClassNotFoundException e) { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + String agent = request.getHeader("user-agent"); + String referer = request.getHeader("referer"); + String remote = request.getRemoteAddr(); + String msg = request.getRequestURL()+" referer="+referer+" user-agent="+agent+" remote="+remote; + if( referer==null ) { + logger.info(msg,e); + } else { + logger.warn(msg,e); + } + return; + } + } + + // Custom headers + addCustomHeaders(response); + + AuthorizingServlet auth = servlet instanceof AuthorizingServlet ? (AuthorizingServlet)servlet : null; + if( isCaching ) { + String etagS = request.getHeader("If-None-Match"); + if( etagS != null ) { + String prevEtag = null; + for( String etag : etagS.split("\\s*,\\s*") ) { + if( etag.equals(prevEtag) ) + continue; + prevEtag = etag; + if( etag.length()>=2 && etag.charAt(0)=='"' && etag.charAt(etag.length()-1)=='"' ) + etag = etag.substring(1,etag.length()-1); + String authKey = null; + if( etag.length()>=2 && etag.charAt(0)=='[' ) { + int i = etag.indexOf(']'); + if( i > 0 ) { + if( auth != null ) + authKey = etag.substring(1,i); + etag = etag.substring(i+1); + } + } + String[] events = etag.split("~"); + long lastModified = getLastModified(events); + try { + if( lastModified <= request.getDateHeader("If-Modified-Since") ) { + if( authKey==null || authorize(auth,authKey,request,response) ) + response.sendError(HttpServletResponse.SC_NOT_MODIFIED); + return; + } + } catch(RuntimeException e) { + handleException(request,e); + } + } + } + } + String authKey = auth==null ? null : getAuthorizationKey(auth,request); + if( authKey != null ) { + if( !authorize(auth,authKey,request,response) ) + return; + request.setAttribute(authKeyAttr,authKey); + } + + if( servlet instanceof CanonicalUrl ) { + CanonicalUrl srv = (CanonicalUrl)servlet; + StringBuffer currentUrl = request.getRequestURL(); + int i = currentUrl.indexOf(";"); + if( i != -1 ) + currentUrl.setLength(i); + String query = request.getQueryString(); + if( query != null ) + currentUrl.append( '?' ).append( query ); + try { + String canonicalUrl = srv.getCanonicalUrl(request); + if( canonicalUrl != null && !currentUrl.toString().equals(canonicalUrl) ) { + response.setHeader("Location",canonicalUrl); + response.sendError( HttpServletResponse.SC_MOVED_PERMANENTLY ); + return; + } + } catch(RuntimeException e) { + logger.warn("couldn't get canonical url",e); + } + } + + request.setAttribute("servlet",servlet); + + try { + if (customWrappers != null) { + RequestAndResponse rr = customWrappers.wrap(request, response); + request = rr.request; + response = rr.response; + } + servlet.service(request,response); + } catch(RuntimeException e) { + handleException(request,e); + } catch(ServletException e) { + handleException(request,e); + } + } + + public void setEtag( HttpServletRequest request, HttpServletResponse response, String... modifyingEvents ) { + if( modifyingEvents.length == 0 ) + modifyingEvents = noModifyingEvents; + StringBuilder buf = new StringBuilder(); + String authKey = (String)request.getAttribute(authKeyAttr); + if( authKey != null ) + buf.append( '[' ).append( authKey).append( ']' ); + buf.append( modifyingEvents[0] ); + for( int i=1; i<modifyingEvents.length; i++ ) { + buf.append( '~' ).append( modifyingEvents[i] ); + } + response.setHeader("Etag",buf.toString()); + long lastModified = getLastModified(modifyingEvents); + response.setDateHeader("Last-Modified",lastModified); + response.setHeader("Cache-Control","max-age=0"); + } + + private boolean authorize(AuthorizingServlet auth,String authKey,HttpServletRequest request,HttpServletResponse response) + throws IOException, ServletException + { + try { + if (customWrappers != null) { + RequestAndResponse rr = customWrappers.wrap(request, response); + request = rr.request; + response = rr.response; + } + return auth.authorize(authKey,request,response); + } catch(RuntimeException e) { + handleException(request,e); + } catch(ServletException e) { + handleException(request,e); + } + throw new RuntimeException("never"); + } + + private String getAuthorizationKey(AuthorizingServlet auth,HttpServletRequest request) + throws ServletException + { + try { + return auth.getAuthorizationKey(request); + } catch(RuntimeException e) { + handleException(request,e); + } catch(ServletException e) { + handleException(request,e); + } + return null; // never gets here + } + + private long getLastModified(String[] modifyingEvents) { + long[] lastModifieds = httpCache.lastModifieds(modifyingEvents); + long lastModified = lastModifieds[0]; + for( int i=1; i<lastModifieds.length; i++ ) { + long lm = lastModifieds[i]; + if( lastModified < lm ) + lastModified = lm; + } + return lastModified; + } + + /** Adds all custom headers to the response object. */ + private void addCustomHeaders(HttpServletResponse response) { + Set<Map.Entry<String, String>> entries = this.customHeaders.entrySet(); + for (Map.Entry<String, String> entry : entries) { + response.setHeader(entry.getKey(), entry.getValue()); + } + } + + private void handleException(HttpServletRequest request,RuntimeException e) + throws ServletException + { + JtpRuntimeException rte; + try { + String agent = request.getHeader("user-agent"); + if( agent == null ) + throw new JtpServletException(request,"null agent",e); + if (!isValidAgent(agent)) + throw new JtpServletException(request, "bad agent " + agent, e); + String remote = request.getRemoteAddr(); + String referer = request.getHeader("referer"); + StringBuilder buf = new StringBuilder() + .append( "method=" ).append( request.getMethod() ) + .append( " user-agent=" ).append( agent ) + .append( " referer=" ).append( referer ) + .append( " remote=" ).append( remote ) + ; + String etag = request.getHeader("If-None-Match"); + if( etag != null ) + buf.append( " etag=[" ).append( etag ).append( "]" ); + if( referer==null || isInIpList(remote) ) + throw new JtpServletException(request,buf.toString(),e); + rte = new JtpRuntimeException(request,buf.toString(),e); + } catch(RuntimeException e2) { + logger.error("failed to handle",e); + throw e2; + } + throw rte; + } + + private static void handleException(HttpServletRequest request,ServletException e) + throws ServletException + { + String agent = request.getHeader("user-agent"); + throw new JtpServletException(request,"user-agent="+agent+" method="+request.getMethod()+" referer="+request.getHeader("referer"),e); + } + + private static class JtpRuntimeException extends RuntimeException { + private JtpRuntimeException(HttpServletRequest request,String msg,RuntimeException e) { + super("url="+getCurrentURL(request)+" "+msg,e); + } + } + + private static class JtpServletException extends ServletException { + private JtpServletException(HttpServletRequest request,String msg,Exception e) { + super("url="+getCurrentURL(request)+" "+msg,e); + } + } + + // work-around jetty bug + private static String getCurrentURL(HttpServletRequest request) { + try { + return ServletUtils.getCurrentURL(request); + } catch(RuntimeException e) { + logger.warn("jetty screwed up",e); + return "[failed]"; + } + } + + private static boolean isValidAgent(String agent) { + if (agent == null) + return false; + for (String badAgent : badAgents) { + if (agent.indexOf(badAgent) >= 0) + return false; + } + return true; + } + + private static final String[] badAgents = new String[]{ + "MJ12bot", + "WISEnutbot", + "Win98", // not worth handling these + "Windows 98", + "Windows 95", + "RixBot", + "User-Agent", // from corrupt header + "Firefox/0", // ancient version of Firefox + "Firefox/2.", // ancient version of Firefox + "Firefox/3.", // ancient version of Firefox + "Opera 7.", // ancient version of Opera + "Opera/7.", + "Opera 8.", + "Opera/8.", + "Opera/9.", + "TwitterFeed 3", + "NAVER Blog Rssbot", + "AOL 9.0", + "rssreader@newstin.com", + "PHPCrawl", + "MSIE 2.", + "MSIE 4.", + "MSIE 5.", + "MSIE 6.", + "MSIE 7.0", + "Mozilla/0.", + "Mozilla/2.0", + "Mozilla/3.0", + "Mozilla/4.6", + "Mozilla/4.7", + "RSSIncludeBot/1.0", // cause exceptions in xml feeds + "Powermarks", + "GenwiFeeder", + "Akregator", + "ia_archiver", + "Atomic_Email_Hunter", + "Yahoo! Slurp", + "Python-urllib", + "BlackBerry", + "SimplePie", // Feeds parser + "www.webintegration.at", // crazy bot + "www.run4dom.com", // crazy bot + "zia-httpmirror", + "POE-Component-Client-HTTP", + "anonymous", + "Sosospider", + "Java/1.6", + "Shareaza", + "Jakarta Commons-HttpClient", + "Apache-HttpClient", + "Baiduspider", + "bingbot", + "MLBot", // www.metadatalabs.com/mlbot + "www.vbseo.com", + "yacybot", // yacy.net/bot.html + "SearchBot" + }; + + private static boolean isBot(String agent) { + if (agent == null) + return false; + for (String bot : bots) { + if (agent.indexOf(bot) >= 0) + return true; + } + return false; + } + + private static final String[] bots = new String[]{ + "Googlebot" + }; + + private HttpServlet getServlet(String path) + throws ServletException, ClassNotFoundException, IOException + { + int i = path.lastIndexOf('.'); + if( i == -1 ) + throw new ClassNotFoundException(path); + return getServletFromClass( + base + path.substring(0,i).replace('/','.') + ); + } + + private HttpServlet getServletFromClass(String cls) + throws ClassNotFoundException + { + synchronized(lock) { + if( reload && hasChanged(cls) ) { + unloadServlets(); + } + HttpServlet srv = map.get(cls); + if( srv==null ) { + try { + Class clas = reload ? cl.loadClass(cls) : Class.forName(cls); + srv = (HttpServlet)clas.newInstance(); + } catch(IllegalAccessException e) { + throw new RuntimeException(e); + } catch(InstantiationException e) { + throw new RuntimeException(e); + } + try { + srv.init(this); + } catch(ServletException e) { + throw new RuntimeException(e); + } + map.put(cls,srv); + } + return srv; + } + } + + private boolean hasChanged(String cls) { + try { + URL url = cl.getResource( SimpleClassLoader.classToResource(cls) ); + if( url==null ) + return true; + File file = new File(url.getPath()); + if( recompile ) { + String path = file.toString(); + if( !path.endsWith(".class") ) + throw new RuntimeException(); + File dir = file.getParentFile(); + String base = path.substring(0,path.length()-6); + File source = new File( base + ".jtp" ); + if( source.lastModified() > clTime ) { + Process proc = Runtime.getRuntime().exec(new String[]{ + "java", "fschmidt.tools.Jtp", source.getName() + },null,dir); + ProcUtils.checkProc(proc); + } + source = new File( base + ".java" ); + if( source.lastModified() > clTime ) { + Process proc = Runtime.getRuntime().exec(new String[]{ + "javac", "-g", source.getName() + },null,dir); + ProcUtils.checkProc(proc); + } + } + return file.lastModified() > clTime; + } catch(IOException e) { + throw new RuntimeException(e); + } + } + + + private long timeLimit = 0; + private static final String timeLimitAttr = "time-limit"; + + private static class TimeLimit { + long timeLimit; + final long startTime = System.currentTimeMillis(); + long ioTime = 0L; + + TimeLimit(long timeLimit) { + this.timeLimit = timeLimit; + } + } + + public long getTimeLimit() { + return timeLimit; + } + + public void setTimeLimit(long timeLimit) { + this.timeLimit = timeLimit; + } + + private TimeLimit startTimeLimit(HttpServletRequest request) { + TimeLimit tl = new TimeLimit(timeLimit); + request.setAttribute( timeLimitAttr, tl ); + return tl; + } + + public void setTimeLimit(HttpServletRequest request,long timeLimit) { + TimeLimit tl = (TimeLimit)request.getAttribute(timeLimitAttr); + tl.timeLimit = timeLimit; + } + + private void checkTimeLimit(HttpServletRequest request) { + TimeLimit tl = (TimeLimit)request.getAttribute(timeLimitAttr); + if( tl.timeLimit == 0L ) + return; + long time = System.currentTimeMillis() - tl.startTime - tl.ioTime; + if( time > tl.timeLimit ) { + float free = Runtime.getRuntime().freeMemory(); + float total = Runtime.getRuntime().totalMemory(); + float used = (total - free) * 100f; + logger.error(ServletUtils.getCurrentURL(request,100) + " took " + time + " ms | " + String.format("%.1f",used/total) + '%'); +/* + Scheduler scheduler = TheScheduler.get(); + if( scheduler instanceof ProfilingScheduler ) { + ProfilingScheduler profilingScheduler = (ProfilingScheduler)scheduler; + if( profilingScheduler.getMode()==ProfilingScheduler.Mode.FOREGROUND ) { + profilingScheduler.captureCPUSnapshot(); + } + } +*/ + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/MemCache.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,77 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import java.util.Map; +import java.util.Collections; +import fschmidt.db.util.WeakCacheMap; + + +public final class MemCache implements HttpCache { + private final HttpCache cache; + private final Map<String,Long> map = Collections.synchronizedMap(new WeakCacheMap<String,Long>()); + + public MemCache(HttpCache cache) { + this.cache = cache; + } + + public long[] lastModifieds(String[] modifyingEvents) { + long[] rtn = new long[modifyingEvents.length]; + int[] missed = new int[modifyingEvents.length]; + int nMissed = 0; + for( int i=0; i<modifyingEvents.length; i++ ) { + Object modifyingEvent = modifyingEvents[i]; + Long r = map.get(modifyingEvent); + if( r!=null ) { + rtn[i] = r.longValue(); + } else { + missed[nMissed++] = i; + } + } + if( nMissed > 0 ) { + String[] aS = new String[nMissed]; + for( int i=0; i<nMissed; i++ ) { + aS[i] = modifyingEvents[missed[i]]; + } + long[] aL = cache.lastModifieds(aS); + for( int i=0; i<nMissed; i++ ) { + int iMissed = missed[i]; + long lastModified = aL[i]; + rtn[iMissed] = lastModified; + map.put(modifyingEvents[iMissed],new Long(lastModified)); + } + } + return rtn; + } + + public void modified(String modifyingEvent) { + cache.modified(modifyingEvent); + map.remove(modifyingEvent); + } + + public void clear() { + cache.clear(); + map.clear(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/NoCache.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,42 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + + +public final class NoCache implements HttpCache { + + public long[] lastModifieds(final String[] modifyingEvents) { + long[] rtn = new long[modifyingEvents.length]; + for( int i=0; i<modifyingEvents.length; i++ ) { + rtn[i] = System.currentTimeMillis(); + } + return rtn; + } + + public void modified(String modifyingEvent) { + } + + public void clear() { + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/NoCacheDefaultServlet.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,26 @@ +package fschmidt.util.servlet; + +import org.eclipse.jetty.http.HttpContent; +import org.eclipse.jetty.servlet.DefaultServlet; +import org.eclipse.jetty.util.resource.Resource; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Enumeration; + +public class NoCacheDefaultServlet extends DefaultServlet { + + protected void sendData(HttpServletRequest request, + HttpServletResponse response, + boolean include, + Resource resource, + HttpContent content, + Enumeration reqRanges) + throws IOException + { + response.setHeader("Cache-Control", "max-age=0"); + super.sendData(request, response, include, resource, content, reqRanges); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/PatternRedirectFilter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,64 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class PatternRedirectFilter implements Filter { + + private Pattern pattern; + private String replacement; + + public void init(FilterConfig filterConfig) throws ServletException { + pattern = Pattern.compile( filterConfig.getInitParameter("pattern") ); + replacement = filterConfig.getInitParameter("replacement"); + } + + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest request = (HttpServletRequest) req; + HttpServletResponse response = (HttpServletResponse) res; + String currentUrl = ServletUtils.getCurrentURL(request); + Matcher matcher = pattern.matcher(currentUrl); + if (matcher.find()) { + String goUrl = matcher.replaceFirst(replacement); + response.setHeader("Location", goUrl); + response.sendError(HttpServletResponse.SC_MOVED_PERMANENTLY); + } else + chain.doFilter(request, response); + } + + public void destroy() {} +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/RedirectFilter.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,105 @@ +/* +Copyright (c) 2009 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + + +public final class RedirectFilter implements Filter { + private String host; + + public void init(FilterConfig filterConfig) + throws ServletException + { + host = filterConfig.getInitParameter("host"); + } + + public void destroy() {} + + public void doFilter( + ServletRequest req, + ServletResponse res, + FilterChain chain + ) + throws IOException, ServletException + { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + MyResponseWrapper wrappedResponse = new MyResponseWrapper(request,response); + try { + chain.doFilter(request,wrappedResponse); + } catch(RedirException e) {} + } + + private class MyResponseWrapper extends HttpServletResponseWrapper { + private final HttpServletRequest request; + + MyResponseWrapper(HttpServletRequest request,HttpServletResponse response) { + super(response); + this.request = request; + } + + public void sendError(int sc, String msg) + throws IOException + { + if( sc==SC_NOT_FOUND || sc==SC_GONE ) + redir(); + else + super.sendError(sc,msg); + } + + public void sendError(int sc) + throws IOException + { + if( sc==SC_NOT_FOUND || sc==SC_GONE ) + redir(); + else + super.sendError(sc); + } + + private void redir() + throws IOException + { + String url = ServletUtils.getCurrentURL(request); + String context = ServletUtils.getContextURL(request); + if( !url.startsWith(context) ) + throw new RuntimeException("context="+context+" url="+url); + String redir = host + url.substring(context.length()); + setHeader( "Location", redir ); + sendError( HttpServletResponse.SC_MOVED_PERMANENTLY ); + throw new RedirException(); + } + } + + private static class RedirException extends IOException {} + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/RedirectServlet.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,56 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import java.io.*; +import java.util.regex.*; +import javax.servlet.*; +import javax.servlet.http.*; +import fschmidt.util.servlet.*; + + +public final class RedirectServlet extends HttpServlet { + private Pattern regex; + private String subst; + private int group = 1; + + public void init(ServletConfig config) + throws ServletException + { + super.init(config); + regex = Pattern.compile( config.getInitParameter("regex") ); + subst = config.getInitParameter("subst"); + } + + protected void service(HttpServletRequest request,HttpServletResponse response) + throws ServletException, IOException + { + String url = ServletUtils.getCurrentURL(request); + Matcher m = regex.matcher(url); + if( !m.find() ) + throw new RuntimeException(url); + url = url.substring(0,m.start(group)) + subst + url.substring(m.end(group)); +//log.info(url); + response.sendRedirect(url); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/ResourceServlet.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,74 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import java.io.*; +import java.net.*; +import javax.servlet.*; +import javax.servlet.http.*; +import fschmidt.util.java.*; + + +public class ResourceServlet extends HttpServlet { + private static final long started = System.currentTimeMillis()/1000*1000; + private String welcomeFile; + + public void init(ServletConfig config) + throws ServletException + { + super.init(config); + welcomeFile = config.getInitParameter("welcome-file"); + } + + protected void service(HttpServletRequest request,final HttpServletResponse response) + throws ServletException, IOException + { + ServletContext context = getServletConfig().getServletContext(); + String path = request.getServletPath(); + if( path.endsWith("/") ) { + context.getRequestDispatcher( path + welcomeFile ).forward(request,response); + return; + } + if( started <= request.getDateHeader("If-Modified-Since") ) { + response.sendError(HttpServletResponse.SC_NOT_MODIFIED); + return; + } + response.setDateHeader("Last-Modified",started); + String type = context.getMimeType(path); + if( type != null ) + response.setHeader("Content-Type",type); + String resource = context.getRealPath(path).replace(File.separatorChar,'/'); + if( resource.startsWith("/") ) + resource = resource.substring(1); + URL url = IoUtils.getUrlFromResource(resource); + if( url==null ) { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + return; + } + ServletOutputStream out = response.getOutputStream(); + InputStream in = url.openStream(); + IoUtils.copyAll(in,out); + in.close(); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/ServletUtils.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,271 @@ +/* +Copyright (c) 2008 Franklin Schmidt <fschmidt@gmail.com> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +package fschmidt.util.servlet; + +import fschmidt.util.java.Base64; +import fschmidt.util.java.HtmlUtils; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpUtils; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; + + +public final class ServletUtils { + + private ServletUtils() { throw new RuntimeException(); } + + public static String getQueryString(HttpServletRequest request) { + return getQueryString(request,0); + } + + public static String getQueryString(HttpServletRequest request,int maxValueLen) { + String method = request.getMethod(); + if( method.equals("GET") ) + return request.getQueryString(); + if( !method.equals("POST") && !method.equals("HEAD") ) + throw new RuntimeException(method); + Enumeration en = request.getParameterNames(); + StringBuilder queryBuf = new StringBuilder(); + if( !en.hasMoreElements() ) + return null; + do { + String param = (String)en.nextElement(); + String value = request.getParameter(param); + if( maxValueLen > 0 ) { + int len = value.length(); + if( len > maxValueLen ) + value = value.substring(0,maxValueLen) + "..." + (len-maxValueLen); + } + queryBuf.append(param); + queryBuf.append('='); + queryBuf.append(value); + queryBuf.append('&'); + } while( en.hasMoreElements() ); + queryBuf.deleteCharAt(queryBuf.length() - 1); + return queryBuf.toString(); + } + + public static String getCurrentURL(HttpServletRequest request) { + return getCurrentURL(request,0); + } + + public static String getCurrentURL(HttpServletRequest request,int maxValueLen) { +// StringBuffer buf = HttpUtils.getRequestURL(request); + StringBuffer buf = request.getRequestURL(); + String qStr = getQueryString(request,maxValueLen); + if(qStr != null && qStr.length() > 0) { + buf.append('?'); + buf.append(qStr); + } + return buf.toString(); + } + + public static String fullURL(HttpServletRequest request,String relativeURL) + throws MalformedURLException + { + return new URL(new URL(request.getRequestURL().toString()),relativeURL).toString(); + } + + public static String getHost(HttpServletRequest request) { +/* + String host = request.getHeader("host"); + if( host.endsWith(":80") ) + host = host.substring(0,host.length()-3); + return host; +*/ + String host = request.getServerName(); + int port = request.getServerPort(); + if( port != 80 ) { + host += ":" + port; + } + return host; + } + + static String getContextUrl(String scheme,String host,String contextPath) { + StringBuilder buf = new StringBuilder(); + buf.append( scheme ); + buf.append( "://" ); + buf.append( host ); + buf.append( contextPath ); + return buf.toString(); + } + + public static String getContextURL(HttpServletRequest request) { + return getContextUrl( request.getScheme(), getHost(request), request.getContextPath() ); + } + + public static String getServletPath(HttpServletRequest request,String relativeURL) + throws MalformedURLException + { + int i = relativeURL.indexOf('?'); + if( i != -1 ) + relativeURL = relativeURL.substring(0,i); + String url = fullURL(request,relativeURL); + String context = getContextURL(request); + if( !url.startsWith(context) ) + throw new RuntimeException("context="+context+" url="+url); + return url.substring(context.length()); + } + + public static Map<String,String[]> getParameterMap(String url) { + int i = url.indexOf('?'); + if( i == -1 ) + return Collections.emptyMap(); + String query = url.substring(i+1); + return parseQueryString(query); + } + + @SuppressWarnings("unchecked") + private static Map<String,String[]> parseQueryString(String query) { + return HttpUtils.parseQueryString(query); + } + + public static String getQueryString(Map<String,String[]> params) { + StringBuilder buf = new StringBuilder(); + for( Iterator<Map.Entry<String,String[]>> iter=params.entrySet().iterator(); iter.hasNext(); ) { + Map.Entry<String,String[]> entry = iter.next(); + String name = entry.getKey(); + String[] values = entry.getValue(); + for( int i=0; i<values.length; i++ ) { + if( buf.length() > 0 ) + buf.append( '&' ); + buf.append( name ); + buf.append( '=' ); + buf.append( HtmlUtils.urlEncode(values[i]) ); + } + } + return buf.toString(); + } + + private static String escape(String value) { + return value.replaceAll(";", "%3B"); + } + + private static String unescape(String value) { + return value.replaceAll("%3B", ";"); + } + + private static Cookie getCookie(HttpServletRequest request,String name) { + Cookie[] cookies = request.getCookies(); + if( cookies == null ) + return null; + for (Cookie cookie : cookies) { + if (cookie.getName().equals(name)) + return cookie; + } + return null; + } + + public static String getCookieValue(HttpServletRequest request,String name) { + Cookie cookie = getCookie(request,name); + return cookie==null ? null : unescape(cookie.getValue()); + } + + public static void setCookie(HttpServletRequest request,HttpServletResponse response,String name,String value,boolean isPersistent, String domain) { + Cookie cookie = getCookie(request,name); + if( cookie==null || !cookie.getValue().equals(value) ) { + cookie = new Cookie(name, escape(value)); + cookie.setPath("/"); + if (domain != null && domain.length() > 0) + cookie.setDomain(domain); + if( isPersistent ) + cookie.setMaxAge(10000000); + response.addCookie(cookie); + } + } + + public static void removeCookie(HttpServletRequest request, + HttpServletResponse response, + String name, + String domain + + ) { + Cookie cookie = getCookie(request, name); + if(cookie != null) { + Cookie delCookie = new Cookie(name, "delete"); + delCookie.setPath("/"); + delCookie.setMaxAge(0); + if (domain != null && domain.length() > 0) + delCookie.setDomain(domain); + response.addCookie(delCookie); + } + } + + + public static String getRemoteAddr(HttpServletRequest request) { + String addr = request.getHeader("X-Forwarded-For"); + if( addr==null ) + addr = request.getRemoteAddr(); + return addr; + } + +/* + public static boolean authenticate(HttpServletRequest request,HttpServletResponse response,String authRealm,String authUsernameAndPassword) + throws IOException + { + String auth = request.getHeader("Authorization"); + if( auth==null ) { + response.setHeader("WWW-Authenticate","Basic realm=\""+authRealm+"\""); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + String[] a = auth.split(" +"); + if( a.length != 2 ) + throw new RuntimeException("auth = "+auth); + if( !a[0].equals("Basic") ) + throw new RuntimeException("auth = "+auth); + if( !new String(Base64.decode(a[1])).equals(authUsernameAndPassword) ) { + response.setHeader("WWW-Authenticate","Basic realm=\""+authRealm+"\""); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return false; + } + return true; + } +*/ + public static String getAuthorization(HttpServletRequest request) { + String auth = request.getHeader("Authorization"); + if( auth==null ) + return null; + String[] a = auth.split(" +"); + if( a.length != 2 ) + throw new RuntimeException("auth = "+auth); + if( !a[0].equals("Basic") ) + throw new RuntimeException("auth = "+auth); + return new String(Base64.decode(a[1])); + } + + public static void sendAuthenticate(HttpServletResponse response,String authRealm) + throws IOException + { + response.setHeader("WWW-Authenticate","Basic realm=\""+authRealm+"\""); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/UrlMapper.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,8 @@ +package fschmidt.util.servlet; + +import javax.servlet.http.HttpServletRequest; + + +public interface UrlMapper { + public UrlMapping getUrlMapping(HttpServletRequest request); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/fschmidt/util/servlet/UrlMapping.java Sun Oct 05 17:24:15 2025 -0600 @@ -0,0 +1,14 @@ +package fschmidt.util.servlet; + +import java.util.Map; + + +public final class UrlMapping { + public final Class servletClass; + public final Map<String,String[]> parameterMap; + + public UrlMapping(Class servletClass,Map<String,String[]> parameterMap) { + this.servletClass = servletClass; + this.parameterMap = parameterMap; + } +}
--- a/src/nabble/utils/Jetty.java Wed Nov 06 21:21:53 2024 -0700 +++ b/src/nabble/utils/Jetty.java Sun Oct 05 17:24:15 2025 -0600 @@ -91,7 +91,7 @@ } public void setResourceBase(ServletContextHandler context,String name) throws MalformedURLException { - URL url = IoUtils.getUrlFromResource(name); + URL url = IoUtils.getUrlFromResource(name); url = new URL(url,"."); context.setResourceBase(url.toString()); }