pax_global_header00006660000000000000000000000064135645000430014512gustar00rootroot0000000000000052 comment=3f1fdd494e369919f32e9cc7e31a014327b19e54 jsamp-jsamp-1.3.7/000077500000000000000000000000001356450004300137445ustar00rootroot00000000000000jsamp-jsamp-1.3.7/.gitignore000066400000000000000000000000771356450004300157400ustar00rootroot00000000000000target velocity.log /.settings /.classpath /.project .DS_Store jsamp-jsamp-1.3.7/README.md000066400000000000000000000010221356450004300152160ustar00rootroot00000000000000JSAMP ===== JSAMP is a java library and toolkit for using SAMP, the Simple Application Messaging Protocol. JSAMP comprises a client library, a command-line toolkit, and a hub implementation. Find documentation for this package at http://www.star.bristol.ac.uk/~mbt/jsamp/. For more information on SAMP, see: * The SAMP standard, http://www.ivoa.net/documents/SAMP/ * SAMP info at IVOA, http://www.ivoa.net/samp/ JSAMP is currently developed by [Mark Taylor](http://www.star.bris.ac.uk/~mbt/) (m.b.taylor@bristol.ac.uk) jsamp-jsamp-1.3.7/pom.xml000066400000000000000000000174461356450004300152750ustar00rootroot00000000000000 4.0.0 org.astrogrid jsamp 1.3.7 JSAMP http://www.star.bristol.ac.uk/~mbt/jsamp/ scm:git:https://github.com/mbtaylor/jsamp.git scm:git:git@github.com:mbtaylor/jsamp.git Various components used for developing and deploying Simple Applications Messaging Protocol-compliant applications and middleware. 2008 mbt Mark Taylor m.b.taylor@bristol.ac.uk http://www.star.bristol.ac.uk/~mbt/ Bristol University ${project.build.directory}/docs yyyy/MM/dd HH:mm:ss UTF-8 1.3.7 www.star.bristol.ac.uk file:///homeb/mbt/public_html/jsamp/ src/java src/test/java src/resources true maven-surefire-plugin 2.16 -ea false false org/astrogrid/samp/**/*Test.java maven-compiler-plugin 3.1 1.4 1.4 maven-jar-plugin 2.4 true org.astrogrid.samp.JSamp all-permissions JSAMP ${maven.build.timestamp} maven-antrun-plugin pre-site Generating command usage strings @{command} run ant ant 1.6.5 ant-contrib ant-contrib 1.0b2 maven-deploy-plugin 2.8.1 maven-javadoc-plugin 2.9.1 http://docs.oracle.com/javase/6/docs/api/ http://ws.apache.org/xmlrpc/xmlrpc2/apidocs/ http://docs.oracle.com/javase/6/docs/api/ ${project.build.sourceDirectory}/../docs/packagelists/j2se http://ws.apache.org/xmlrpc/xmlrpc2/apidocs/ ${project.build.sourceDirectory}/../docs/packagelists/xmlrpc 1.4 maven-project-info-reports-plugin 2.7 https://github.com/mbtaylor/jsamp xmlrpc xmlrpc 1.2-b1 junit junit 3.8 test jsamp-jsamp-1.3.7/src/000077500000000000000000000000001356450004300145335ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/docs/000077500000000000000000000000001356450004300154635ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/docs/packagelists/000077500000000000000000000000001356450004300201355ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/docs/packagelists/j2se/000077500000000000000000000000001356450004300210005ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/docs/packagelists/j2se/package-list000066400000000000000000000052071356450004300232730ustar00rootroot00000000000000java.applet java.awt java.awt.color java.awt.datatransfer java.awt.dnd java.awt.event java.awt.font java.awt.geom java.awt.im java.awt.im.spi java.awt.image java.awt.image.renderable java.awt.print java.beans java.beans.beancontext java.io java.lang java.lang.ref java.lang.reflect java.math java.net java.nio java.nio.channels java.nio.channels.spi java.nio.charset java.nio.charset.spi java.rmi java.rmi.activation java.rmi.dgc java.rmi.registry java.rmi.server java.security java.security.acl java.security.cert java.security.interfaces java.security.spec java.sql java.text java.util java.util.jar java.util.logging java.util.prefs java.util.regex java.util.zip javax.accessibility javax.crypto javax.crypto.interfaces javax.crypto.spec javax.imageio javax.imageio.event javax.imageio.metadata javax.imageio.plugins.jpeg javax.imageio.spi javax.imageio.stream javax.naming javax.naming.directory javax.naming.event javax.naming.ldap javax.naming.spi javax.net javax.net.ssl javax.print javax.print.attribute javax.print.attribute.standard javax.print.event javax.rmi javax.rmi.CORBA javax.security.auth javax.security.auth.callback javax.security.auth.kerberos javax.security.auth.login javax.security.auth.spi javax.security.auth.x500 javax.security.cert javax.sound.midi javax.sound.midi.spi javax.sound.sampled javax.sound.sampled.spi javax.sql javax.swing javax.swing.border javax.swing.colorchooser javax.swing.event javax.swing.filechooser javax.swing.plaf javax.swing.plaf.basic javax.swing.plaf.metal javax.swing.plaf.multi javax.swing.table javax.swing.text javax.swing.text.html javax.swing.text.html.parser javax.swing.text.rtf javax.swing.tree javax.swing.undo javax.transaction javax.transaction.xa javax.xml.parsers javax.xml.transform javax.xml.transform.dom javax.xml.transform.sax javax.xml.transform.stream org.ietf.jgss org.omg.CORBA org.omg.CORBA.DynAnyPackage org.omg.CORBA.ORBPackage org.omg.CORBA.TypeCodePackage org.omg.CORBA.portable org.omg.CORBA_2_3 org.omg.CORBA_2_3.portable org.omg.CosNaming org.omg.CosNaming.NamingContextExtPackage org.omg.CosNaming.NamingContextPackage org.omg.Dynamic org.omg.DynamicAny org.omg.DynamicAny.DynAnyFactoryPackage org.omg.DynamicAny.DynAnyPackage org.omg.IOP org.omg.IOP.CodecFactoryPackage org.omg.IOP.CodecPackage org.omg.Messaging org.omg.PortableInterceptor org.omg.PortableInterceptor.ORBInitInfoPackage org.omg.PortableServer org.omg.PortableServer.CurrentPackage org.omg.PortableServer.POAManagerPackage org.omg.PortableServer.POAPackage org.omg.PortableServer.ServantLocatorPackage org.omg.PortableServer.portable org.omg.SendingContext org.omg.stub.java.rmi org.w3c.dom org.xml.sax org.xml.sax.ext org.xml.sax.helpers jsamp-jsamp-1.3.7/src/docs/packagelists/xmlrpc/000077500000000000000000000000001356450004300214425ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/docs/packagelists/xmlrpc/package-list000066400000000000000000000001731356450004300237320ustar00rootroot00000000000000org.apache.xmlrpc org.apache.xmlrpc.applet org.apache.xmlrpc.secure org.apache.xmlrpc.secure.sunssl org.apache.xmlrpc.util jsamp-jsamp-1.3.7/src/java/000077500000000000000000000000001356450004300154545ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/000077500000000000000000000000001356450004300162435ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/000077500000000000000000000000001356450004300202415ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/000077500000000000000000000000001356450004300212015ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Client.java000066400000000000000000000012211356450004300232560ustar00rootroot00000000000000package org.astrogrid.samp; /** * Describes an application registered with a SAMP hub. * * @author Mark Taylor * @since 14 Jul 2008 */ public interface Client { /** * Returns the public identifier for this client. * * @return public id */ String getId(); /** * Returns the currently declared metadata for this client, if any. * * @return metadata object; may be null */ Metadata getMetadata(); /** * Returns the currently declared subscriptions for this client, if any. * * @return subscriptions object; may be null */ Subscriptions getSubscriptions(); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/DataException.java000066400000000000000000000017731356450004300246040ustar00rootroot00000000000000package org.astrogrid.samp; /** * Unchecked exception thrown when a data structure for use with * SAMP does not have the correct form. * * @author Mark Taylor * @since 15 Jul 2008 */ public class DataException extends IllegalArgumentException { /** * Constructs an exception with no message. */ public DataException() { super(); } /** * Consructs an exception with a given message. * * @param msg message */ public DataException( String msg ) { super( msg ); } /** * Constructs an exception with a given cause. * * @param e cause of this exception */ public DataException( Throwable e ) { this(); initCause( e ); } /** * Constructs an exception with a given message and cause. * * @param msg message * @param e cause of this exception */ public DataException( String msg, Throwable e ) { this( msg ); initCause( e ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/ErrInfo.java000066400000000000000000000123051356450004300234110ustar00rootroot00000000000000package org.astrogrid.samp; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.util.Map; /** * Represents the error information associated with a SAMP response. * This corresponds to the samp.error entry in a response map. * * @author Mark Taylor * @since 14 Jul 2008 */ public class ErrInfo extends SampMap { /** Key for short description of what went wrong. */ public static final String ERRORTXT_KEY = "samp.errortxt"; /** Key for free-form text given more information about the error. */ public static final String USERTXT_KEY = "samp.usertxt"; /** Key for debugging information such as a stack trace. */ public static final String DEBUGTXT_KEY = "samp.debugtxt"; /** Key for a numeric or textual code identifying the error. */ public static final String CODE_KEY = "samp.code"; private static final String[] KNOWN_KEYS = new String[] { ERRORTXT_KEY, USERTXT_KEY, DEBUGTXT_KEY, CODE_KEY, }; /** * Constructs an empty ErrInfo. */ public ErrInfo() { super( KNOWN_KEYS ); } /** * Constructs an ErrInfo based on a given Throwable. * * @param e error */ public ErrInfo( Throwable e ) { this(); String txt = e.getMessage(); if ( txt == null || txt.trim().length() == 0 ) { txt = e.getClass().getName(); } put( ERRORTXT_KEY, txt ); put( USERTXT_KEY, e.toString() ); put( DEBUGTXT_KEY, getStackTrace( e ) ); put( CODE_KEY, e.getClass().getName() ); } /** * Constructs an ErrInfo based on an existing map. * * @param map map containing initial data for this object */ public ErrInfo( Map map ) { this(); putAll( map ); } /** * Constructs an ErrInfo with a given {@link #ERRORTXT_KEY} value. * * @param errortxt short string describing what went wrong */ public ErrInfo( String errortxt ) { this(); put( ERRORTXT_KEY, errortxt ); } /** * Sets the value for the {@link #ERRORTXT_KEY} key. * * @param errortxt short string describing what went wrong */ public void setErrortxt( String errortxt ) { put( ERRORTXT_KEY, errortxt ); } /** * Returns the value for the {@link #ERRORTXT_KEY} key. * * @return short string describing what went wrong */ public String getErrortxt() { return getString( ERRORTXT_KEY ); } /** * Sets the value for the {@link #USERTXT_KEY} key. * * @param usertxt free-form string giving more detail on the error */ public void setUsertxt( String usertxt ) { put( USERTXT_KEY, usertxt ); } /** * Returns the value for the {@link #USERTXT_KEY} key. * * @return free-form string giving more detail on the error */ public String getUsertxt() { return getString( USERTXT_KEY ); } /** * Sets the value for the {@link #DEBUGTXT_KEY} key. * * @param debugtxt string containing debugging information, such as a * a stack trace */ public void setDebugtxt( String debugtxt ) { put( DEBUGTXT_KEY, debugtxt ); } /** * Returns the value for the {@link #DEBUGTXT_KEY} key. * * @return string containing debugging information, such as a stack trace */ public String getDebugtxt() { return getString( DEBUGTXT_KEY ); } /** * Sets the value for the {@link #CODE_KEY} key. * * @param code numeric or textual code identifying the error */ public void setCode( String code ) { put( CODE_KEY, code ); } /** * Returns the value for the {@link #CODE_KEY} key. * * @return numeric or textual code identifying the error */ public String getCode() { return getString( CODE_KEY ); } public void check() { super.check(); checkHasKeys( new String[] { ERRORTXT_KEY, } ); } /** * Returns a given map as an ErrInfo object. * * @param map map * @return errInfo */ public static ErrInfo asErrInfo( Map map ) { return ( map instanceof ErrInfo || map == null ) ? (ErrInfo) map : new ErrInfo( map ); } /** * Generates a string containing a stack trace for a given throwable. * * @param e error * @return stacktrace */ private static String getStackTrace( Throwable e ) { byte[] bbuf; try { ByteArrayOutputStream bOut = new ByteArrayOutputStream(); e.printStackTrace( new PrintStream( bOut ) ); bOut.close(); bbuf = bOut.toByteArray(); } catch ( IOException ioex ) { assert false; return "error generating stacktrace"; } StringBuffer sbuf = new StringBuffer( bbuf.length ); for ( int ic = 0; ic < bbuf.length; ic++ ) { char c = (char) bbuf[ ic ]; if ( SampUtils.isStringChar( c ) ) { sbuf.append( c ); } } return sbuf.toString(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/JSamp.java000066400000000000000000000250241356450004300230610ustar00rootroot00000000000000package org.astrogrid.samp; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.hub.Hub; import org.astrogrid.samp.web.WebClientProfile; import org.astrogrid.samp.xmlrpc.StandardClientProfile; import org.astrogrid.samp.xmlrpc.XmlRpcKit; /** * Convenience class for invoking JSAMP command-line applications. * * @author Mark Taylor * @since 23 Jul 2008 */ public class JSamp { /** Known command class names. */ static final String[] COMMAND_CLASSES = new String[] { "org.astrogrid.samp.hub.Hub", "org.astrogrid.samp.gui.HubMonitor", "org.astrogrid.samp.util.SampLoad", "org.astrogrid.samp.test.Snooper", "org.astrogrid.samp.test.MessageSender", "org.astrogrid.samp.test.HubTester", "org.astrogrid.samp.test.CalcStorm", "org.astrogrid.samp.bridge.Bridge", }; /** * Private sole constructor prevents instantiation. */ private JSamp() { } /** * Does the work for the main method. */ public static int runMain( String[] args ) { // Assemble usage message. StringBuffer ubuf = new StringBuffer() .append( "\n Usage:" ) .append( "\n " ) .append( JSamp.class.getName() ) .append( " [-help]" ) .append( " [-version]" ) .append( " " ) .append( " [-help]" ) .append( " " ) .append( "\n " ) .append( "" ) .append( " [-help]" ) .append( " " ) .append( "\n" ) .append( "\n Commands (command-classes) are:" ); for ( int ic = 0; ic < COMMAND_CLASSES.length; ic++ ) { String className = COMMAND_CLASSES[ ic ]; ubuf.append( "\n " ) .append( abbrev( className ) ); int pad = 14 - abbrev( className ).length(); for ( int i = 0; i < pad; i++ ) { ubuf.append( ' ' ); } ubuf.append( " (" ) .append( className ) .append( ")" ); } ubuf.append( "\n" ) .append( "\n " ) .append( "Environment Variable:" ) .append( "\n " ) .append( DefaultClientProfile.HUBLOC_ENV ) .append( " = " ) .append( StandardClientProfile.STDPROFILE_HUB_PREFIX ) .append( "" ) .append( "|" ) .append( WebClientProfile.WEBPROFILE_HUB_PREFIX ) .append( "" ) .append( "\n " ) .append( "|" ) .append( DefaultClientProfile.HUBLOC_CLASS_PREFIX ) .append( "" ) .append( "\n" ) .append( "\n " ) .append( "System Properties:" ) .append( "\n " ) .append( "jsamp.hub.profiles = " ) .append( "std|web|[,...]" ) .append( "\n " ) .append( "jsamp.localhost = " ) .append( "\"[hostname]\"|\"[hostnumber]\"|" ) .append( "\n " ) .append( "jsamp.server.port = " ) .append( "" ) .append( "\n " ) .append( "jsamp.web.extrahosts = " ) .append( "[,...]" ) .append( "\n " ) .append( "jsamp.xmlrpc.impl = " ) .append( formatImpls( XmlRpcKit.KNOWN_IMPLS, XmlRpcKit.class ) ) .append( "\n" ); String usage = ubuf.toString(); // Perform general tweaks. setDefaultProperty( "java.awt.Window.locationByPlatform", "true" ); // Process command line arguments. List argList = new ArrayList( Arrays.asList( args ) ); for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.toLowerCase().equals( "hubrunner" ) ) { System.err.println( "\"hubrunner\" command is deprecated. " + "Use \"hub\" instead." ); return 1; } for ( int ic = 0; ic < COMMAND_CLASSES.length; ic++ ) { String className = COMMAND_CLASSES[ ic ]; if ( arg.toLowerCase() .equals( abbrev( className ).toLowerCase() ) ) { it.remove(); return runCommand( className, (String[]) argList.toArray( new String[ 0 ] ) ); } } if ( arg.startsWith( "-h" ) ) { System.out.println( usage ); return 0; } else if ( arg.startsWith( "-vers" ) ) { System.out.println(); System.out.println( getVersionText() ); System.out.println(); return 0; } else { System.err.println( usage ); return 1; } } // No arguments. assert argList.isEmpty(); System.err.println( JSamp.class.getName() + " invoked with no arguments" + " - running hub" ); System.err.println( "Use \"-help\" flag for more options" ); System.err.println( "Use \"hub\" argument" + " to suppress this message" ); return runCommand( Hub.class.getName(), new String[ 0 ] ); } /** * Runs a command. * * @param className name of a class with a main(String[]) * method * @param args arguments as if passed from the command line */ private static int runCommand( String className, String[] args ) { Class clazz; try { clazz = Class.forName( className ); } catch ( ClassNotFoundException e ) { System.err.println( "Class " + className + " not available" ); return 1; } Object statusObj; try { getMainMethod( clazz ).invoke( null, new Object[] { args } ); return 0; } catch ( InvocationTargetException e ) { e.getCause().printStackTrace(); return 1; } catch ( Exception e ) { e.printStackTrace(); return 1; } } /** * Returns the main(String[]) method for a given class. */ static Method getMainMethod( Class clazz ) { Method method; try { method = clazz.getMethod( "main", new Class[] { String[].class } ); } catch ( NoSuchMethodException e ) { throw (IllegalArgumentException) new AssertionError( "main() method missing for " + clazz.getName() ) .initCause( e ); } int mods = method.getModifiers(); if ( Modifier.isStatic( mods ) && Modifier.isPublic( mods ) && method.getReturnType() == void.class ) { return method; } else { throw new IllegalArgumentException( "Wrong main() method signature" + " for " + clazz.getName() ); } } /** * Returns the abbreviated form of a given class name. * * @param className class name * @return abbreviation */ private static String abbrev( String className ) { return className.substring( className.lastIndexOf( "." ) + 1 ) .toLowerCase(); } private static String formatImpls( Object[] options, Class clazz ) { StringBuffer sbuf = new StringBuffer(); if ( options != null ) { for ( int i = 0; i < options.length; i++ ) { if ( sbuf.length() > 0 ) { sbuf.append( '|' ); } sbuf.append( options[ i ] ); } } if ( clazz != null ) { if ( sbuf.length() > 0 ) { sbuf.append( '|' ); } sbuf.append( '<' ) .append( clazz.getName().replaceFirst( "^.*\\.", "" ) .toLowerCase() ) .append( "-class" ) .append( '>' ); } return sbuf.toString(); } /** * Returns a string giving version details for this package. * * @return version string */ private static String getVersionText() { return new StringBuffer() .append( " This is JSAMP.\n" ) .append( "\n " ) .append( "JSAMP toolkit version:" ) .append( "\n " ) .append( SampUtils.getSoftwareVersion() ) .append( "\n " ) .append( "SAMP standard version:" ) .append( "\n " ) .append( SampUtils.getSampVersion() ) .append( "\n " ) .append( "Author:" ) .append( "\n " ) .append( "Mark Taylor (m.b.taylor@bristol.ac.uk)" ) .append( "\n " ) .append( "WWW:" ) .append( "\n " ) .append( "http://www.star.bristol.ac.uk/~mbt/jsamp" ) .toString(); } /** * Sets a system property to a given value unless it has already been set. * If it has a prior value, that is undisturbed. * Potential security exceptions are caught and dealt with. * * @param key property name * @param value suggested property value */ private static void setDefaultProperty( String key, String value ) { String existingVal = System.getProperty( key ); if ( existingVal == null || existingVal.trim().length() == 0 ) { try { System.setProperty( key, value ); } catch ( SecurityException e ) { // never mind. } } } /** * Main method. * Use -help flag for documentation. */ public static void main( String[] args ) { int status = runMain( args ); if ( status != 0 ) { System.exit( status ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/JsonReader.java000066400000000000000000000200311356450004300240740ustar00rootroot00000000000000/* * Copyright (C) 2011 Miami-Dade County. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Note: this file incorporates source code from 3d party entities. Such code * is copyrighted by those entities as indicated below. */ /* * This code is based on the mjson library found here: * http://www.sharegov.org/mjson/Json.java * http://sharegov.blogspot.com/2011/06/json-library.html */ package org.astrogrid.samp; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Simple JSON parser which only copes with SAMP-friendly JSON, * that is strings, lists and objects. * This code is a stripped-down and somewhat fixed copy of the mjson * libraray written by Borislav Iordanov, from * http://www.sharegov.org/mjson/Json.java. * * @author Borislav Iordanov * @author Mark Taylor */ class JsonReader { private static final Object OBJECT_END = new Token("OBJECT_END"); private static final Object ARRAY_END = new Token("ARRAY_END"); private static final Object COLON = new Token("COLON"); private static final Object COMMA = new Token("COMMA"); public static final int FIRST = 0; public static final int CURRENT = 1; public static final int NEXT = 2; private static Map escapes = new HashMap(); static { escapes.put(new Character('"'), new Character('"')); escapes.put(new Character('\\'), new Character('\\')); escapes.put(new Character('/'), new Character('/')); escapes.put(new Character('b'), new Character('\b')); escapes.put(new Character('f'), new Character('\f')); escapes.put(new Character('n'), new Character('\n')); escapes.put(new Character('r'), new Character('\r')); escapes.put(new Character('t'), new Character('\t')); } private CharacterIterator it; private char c; private Object token; private StringBuffer buf = new StringBuffer(); private char next() { if (it.getIndex() == it.getEndIndex()) throw new DataException("Reached end of input at the " + it.getIndex() + "th character."); c = it.next(); return c; } private char previous() { c = it.previous(); return c; } private void skipWhiteSpace() { do { if (Character.isWhitespace(c)) ; else if (c == '/') { next(); if (c == '*') { // skip multiline comments while (c != CharacterIterator.DONE) if (next() == '*' && next() == '/') break; if (c == CharacterIterator.DONE) throw new DataException("Unterminated comment while parsing JSON string."); } else if (c == '/') while (c != '\n' && c != CharacterIterator.DONE) next(); else { previous(); break; } } else break; } while (next() != CharacterIterator.DONE); } public Object read(CharacterIterator ci, int start) { it = ci; switch (start) { case FIRST: c = it.first(); break; case CURRENT: c = it.current(); break; case NEXT: c = it.next(); break; } return read(); } public Object read(CharacterIterator it) { return read(it, NEXT); } public Object read(String string) { return read(new StringCharacterIterator(string), FIRST); } private Object read() { skipWhiteSpace(); char ch = c; next(); switch (ch) { case '"': token = readString(); break; case '[': token = readArray(); break; case ']': token = ARRAY_END; break; case ',': token = COMMA; break; case '{': token = readObject(); break; case '}': token = OBJECT_END; break; case ':': token = COLON; break; default: { throw new DataException( "Unexpected character '" + ch + "'" ); } } return token; } private Map readObject() { Map ret = new LinkedHashMap(); read(); while (true) { if (token == OBJECT_END) { return ret; } if (!(token instanceof String)) { throw new DataException("Missing/illegal object key"); } String key = (String) token; if (read() != COLON) { throw new DataException("Missing colon in JSON object"); } Object value = read(); ret.put(key, value); read(); if (token == COMMA) { read(); } else if (token != OBJECT_END) { throw new DataException("Unexpected token " + token); } } } private List readArray() { List ret = new ArrayList(); Object value = read(); while (token != ARRAY_END) { ret.add(value); if (read() == COMMA) value = read(); else if (token != ARRAY_END) throw new DataException("Unexpected token in array " + token); } return ret; } private String readString() { buf.setLength(0); while (c != '"') { if (c == '\\') { next(); if (c == 'u') { add(unicode()); } else { Object value = escapes.get(new Character(c)); if (value != null) { add(((Character) value).charValue()); } } } else { add(); } } next(); return buf.toString(); } private void add(char cc) { buf.append(cc); next(); } private void add() { add(c); } private char unicode() { int value = 0; for (int i = 0; i < 4; ++i) { switch (next()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': value = (value << 4) + c - '0'; break; case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': value = (value << 4) + (c - 'a') + 10; break; case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': value = (value << 4) + (c - 'A') + 10; break; } } return (char) value; } /** * Named object. */ private static class Token { private final String name; Token(String name) { this.name = name; } public String toString() { return this.name; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/JsonWriter.java000066400000000000000000000131171356450004300241550ustar00rootroot00000000000000package org.astrogrid.samp; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Outputs a SAMP object as JSON. * Can do it formatted and reasonably compact. * * @author Mark Taylor * @since 25 Jul 2011 */ class JsonWriter { private final int indent_; private final String spc_; /** * Constructor with default properties. */ public JsonWriter() { this( 2, true ); } /** * Custom constructor. * * @param indent number of characters indent per level * @param spacer whether to put spaces inside brackets */ public JsonWriter( int indent, boolean spacer ) { indent_ = indent; spc_ = spacer ? " " : ""; } /** * Converts a SAMP data item to JSON. * * @param item SAMP-friendly object * @return JSON representation */ public String toJson( Object item ) { StringBuffer sbuf = new StringBuffer(); toJson( sbuf, item, 0, false ); if ( indent_ >= 0 ) { assert sbuf.charAt( 0 ) == '\n'; return sbuf.substring( 1, sbuf.length() ); } else { return sbuf.toString(); } } /** * Recursive method which does the work for conversion. * If possible, call this method with isPositioned=false. * * @param sbuf string buffer to append result to * @param item object to convert * @param level current indentation level * @param isPositioned true if output should be direct to sbuf, * false if it needs a newline plus indentation first */ private void toJson( StringBuffer sbuf, Object item, int level, boolean isPositioned ) { if ( item instanceof String ) { if ( ! isPositioned ) { sbuf.append( getIndent( level ) ); } sbuf.append( '"' ) .append( (String) item ) .append( '"' ); } else if ( item instanceof List ) { List list = (List) item; if ( list.isEmpty() ) { if ( ! isPositioned ) { sbuf.append( getIndent( level ) ); } sbuf.append( "[]" ); } else { sbuf.append( getIntroIndent( level, '[', isPositioned ) ); boolean isPos = ! isPositioned; for ( Iterator it = list.iterator(); it.hasNext(); ) { toJson( sbuf, it.next(), level + 1, isPos ); if ( it.hasNext() ) { sbuf.append( "," ); } isPos = false; } sbuf.append( spc_ + "]" ); } } else if ( item instanceof Map ) { Map map = (Map) item; if ( map.isEmpty() ) { if ( ! isPositioned ) { sbuf.append( getIndent( level ) ); } sbuf.append( "{}" ); } else { sbuf.append( getIntroIndent( level, '{', isPositioned ) ); boolean isPos = ! isPositioned; for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); Object key = entry.getKey(); if ( ! ( key instanceof String ) ) { throw new DataException( "Non-string key in map:" + key ); } toJson( sbuf, key, level + 1, isPos ); sbuf.append( ":" + spc_ ); toJson( sbuf, entry.getValue(), level + 1, true ); if ( it.hasNext() ) { sbuf.append( "," ); } isPos = false; } sbuf.append( spc_ + "}" ); } } else { throw new DataException( "Illegal data type " + item ); } } /** * Returns prepended whitespace containing an opener character. * * @param level indentation level * @param chr opener character * @param isPositioned true if output should be direct to sbuf, * false if it needs a newline plus indentation first * @return string to prepend */ private String getIntroIndent( int level, char chr, boolean isPositioned ) { if ( isPositioned ) { return new StringBuffer().append( chr ).toString(); } else { StringBuffer sbuf = new StringBuffer(); sbuf.append( getIndent( level ) ); sbuf.append( chr ); for ( int ic = 0; ic < indent_ - 1; ic++ ) { sbuf.append( ' ' ); } return sbuf.toString(); } } /** * Returns prepended whitespace. * * @param level indentation level * @return string to prepend */ private String getIndent( int level ) { if ( indent_ >= 0 ) { int nc = level * indent_; StringBuffer sbuf = new StringBuffer( nc + 1 ); sbuf.append( '\n' ); for ( int ic = 0; ic < nc; ic++ ) { sbuf.append( ' ' ); } return sbuf.toString(); } else { return ""; } } public static void main( String[] args ) { String txt = args[ 0 ]; Object item = new JsonReader().read( txt ); System.out.println( new JsonWriter().toJson( item ) ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Message.java000066400000000000000000000075151356450004300234400ustar00rootroot00000000000000package org.astrogrid.samp; import java.util.HashMap; import java.util.Map; /** * Represents an encoded SAMP Message. * * @author Mark Taylor * @since 14 Jul 2008 */ public class Message extends SampMap { /** Key for message MType. */ public static final String MTYPE_KEY = "samp.mtype"; /** Key for map of parameters used by this message. */ public static final String PARAMS_KEY = "samp.params"; private static final String[] KNOWN_KEYS = new String[] { MTYPE_KEY, PARAMS_KEY, }; /** * Constructs an empty message. */ public Message() { super( KNOWN_KEYS ); } /** * Constructs a message based on an existing map. * * @param map map containing initial data for this object */ public Message( Map map ) { this(); putAll( map ); } /** * Constructs a message with a given MType and params map. * * @param mtype value for {@link #MTYPE_KEY} key * @param params value for {@link #PARAMS_KEY} key */ public Message( String mtype, Map params ) { this(); put( MTYPE_KEY, mtype ); put( PARAMS_KEY, params == null ? new HashMap() : params ); } /** * Constructs a message with a given MType. * The parameters map will be mutable. * * @param mtype value for {@link #MTYPE_KEY} key */ public Message( String mtype ) { this( mtype, null ); } /** * Returns this message's MType. * * @return value for {@link #MTYPE_KEY} */ public String getMType() { return getString( MTYPE_KEY ); } /** * Sets this message's params map. * * @param params value for {@link #PARAMS_KEY} */ public void setParams( Map params ) { put( PARAMS_KEY, params ); } /** * Returns this message's params map. * * @return value for {@link #PARAMS_KEY} */ public Map getParams() { return getMap( PARAMS_KEY ); } /** * Sets the value for a single entry in this message's * samp.params map. * * @param name param name * @param value param value */ public Message addParam( String name, Object value ) { if ( ! containsKey( PARAMS_KEY ) ) { put( PARAMS_KEY, new HashMap() ); } getParams().put( name, value ); return this; } /** * Returns the value of a single entry in this message's * samp.params map. Null is returned if the parameter * does not appear. * * @param name param name * @return param value, or null */ public Object getParam( String name ) { Map params = getParams(); return params == null ? null : params.get( name ); } /** * Returns the value of a single entry in this message's * samp.params map, throwing an exception * if it is not present. * * @param name param name * @return param value * @throws DataException if no parameter name is present */ public Object getRequiredParam( String name ) { Object param = getParam( name ); if ( param != null ) { return param; } else { throw new DataException( "Required parameter \"" + name + "\" is missing" ); } } public void check() { super.check(); checkHasKeys( new String[] { MTYPE_KEY } ); } /** * Returns a given map as a Message object. * * @param map map * @return message */ public static Message asMessage( Map map ) { return ( map instanceof Message || map == null ) ? (Message) map : new Message( map ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Metadata.java000066400000000000000000000077621356450004300236000ustar00rootroot00000000000000package org.astrogrid.samp; import java.net.MalformedURLException; import java.net.URL; import java.util.Map; /** * Represents the application metadata associated with a SAMP client. * * @author Mark Taylor * @since 14 Jul 2008 */ public class Metadata extends SampMap { /** Key for application name. */ public static final String NAME_KEY = "samp.name"; /** Key for short description of the application in plain text. */ public static final String DESCTEXT_KEY = "samp.description.text"; /** Key for description of the application in HTML. */ public static final String DESCHTML_KEY = "samp.description.html"; /** Key for the URL of an icon in png, gif or jpeg format. */ public static final String ICONURL_KEY = "samp.icon.url"; /** Key for the URL of a documentation web page. */ public static final String DOCURL_KEY = "samp.documentation.url"; private static final String[] KNOWN_KEYS = new String[] { NAME_KEY, DESCTEXT_KEY, DESCHTML_KEY, ICONURL_KEY, DOCURL_KEY, }; /** * Constructs an empty Metadata map. */ public Metadata() { super( KNOWN_KEYS ); } /** * Constructs a Metadata map based on a given map. * * @param map map containing initial values for this object */ public Metadata( Map map ) { this(); putAll( map ); } /** * Sets the value for the application's name. * * @param name value for {@link #NAME_KEY} key */ public void setName( String name ) { put( NAME_KEY, name ); } /** * Returns the value for the application's name. * * @return value for {@link #NAME_KEY} key */ public String getName() { return (String) get( NAME_KEY ); } /** * Sets a short description of the application. * * @param txt value for {@link #DESCTEXT_KEY} key */ public void setDescriptionText( String txt ) { put( DESCTEXT_KEY, txt ); } /** * Returns a short description of the application. * * @return value for {@link #DESCTEXT_KEY} key */ public String getDescriptionText() { return (String) get( DESCTEXT_KEY ); } /** * Sets an HTML description of the application. * * @param html value for {@link #DESCHTML_KEY} key */ public void setDescriptionHtml( String html ) { put( DESCHTML_KEY, html ); } /** * Returns an HTML description of the application. * * @return value for {@link #DESCHTML_KEY} key */ public String getDescriptionHtml() { return (String) get( DESCHTML_KEY ); } /** * Sets a URL for a gif, png or jpeg icon identifying the application. * * @param url value for {@link #ICONURL_KEY} key */ public void setIconUrl( String url ) { put( ICONURL_KEY, url ); } /** * Returns a URL for a gif, png or jpeg icon identifying the application. * * @return value for {@link #ICONURL_KEY} key */ public URL getIconUrl() { return getUrl( ICONURL_KEY ); } /** * Sets a URL for a documentation web page. * * @param url value for {@link #DOCURL_KEY} key */ public void setDocumentationUrl( String url ) { put( DOCURL_KEY, url ); } /** * Returns a URL for a documentation web page. * * @return value for {@link #DOCURL_KEY} key */ public URL getDocumentationUrl() { return getUrl( DOCURL_KEY ); } public void check() { super.check(); SampUtils.checkUrl( getString( DOCURL_KEY ) ); SampUtils.checkUrl( getString( ICONURL_KEY ) ); } /** * Returns a given map as a Metadata object. * * @param map map * @return metadata */ public static Metadata asMetadata( Map map ) { return ( map instanceof Metadata || map == null ) ? (Metadata) map : new Metadata( map ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Platform.java000066400000000000000000000255761356450004300236470ustar00rootroot00000000000000package org.astrogrid.samp; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; import java.util.logging.Logger; /** * Platform-dependent features required by the SAMP implementation. * * @author Mark Taylor * @since 14 Jul 2008 */ public abstract class Platform { private static Platform instance_; private final String name_; private static final Logger logger_ = Logger.getLogger( Platform.class.getName() ); /** * Constructor. * * @param name platform name */ protected Platform( String name ) { name_ = name; } /** * Returns SAMP's definition of the "home" directory. * * @return directory containing SAMP lockfile */ public abstract File getHomeDirectory(); /** * Returns the value of an environment variable. * If it can't be done, null is returned. * * @param varname name of environment variable * @return value of environment variable */ public String getEnv( String varname ) { try { return System.getenv( varname ); } // System.getenv is unimplemented at 1.4, and throws an Error. catch ( Throwable e ) { String[] argv = getGetenvArgs( varname ); if ( argv == null ) { return null; } else { try { String cmdout = exec( argv ); return cmdout.trim(); } catch ( Throwable e2 ) { return null; } } } } /** * Sets file permissions on a given file so that it cannot be read by * anyone other than its owner. * * @param file file whose permissions are to be altered * @throws IOException if permissions cannot be changed */ public void setPrivateRead( File file ) throws IOException { if ( setPrivateReadReflect( file ) ) { return; } else { String[] privateReadArgs = getPrivateReadArgs( file ); if ( privateReadArgs != null ) { exec( privateReadArgs ); } else { logger_.info( "No known way to set user-only read permissions" + "; possible security implications" + " on multi-user systems" ); } } } /** * Returns an array of words to pass to * {@link java.lang.Runtime#exec(java.lang.String[])} in order * to read an environment variable name. * If null is returned, no way is known to do this with a system command. * * @param varname environment variable name to read * @return exec args */ protected abstract String[] getGetenvArgs( String varname ); /** * Returns an array of words to pass to * {@link java.lang.Runtime#exec(java.lang.String[])} in order * to set permissions on a given file so that it cannot be read by * anyone other than its owner. * If null is returned, no way is known to do this with a system command. * * @param file file to alter * @return exec args */ protected abstract String[] getPrivateReadArgs( File file ) throws IOException; /** * Attempt to use the File.setReadable() method to set * permissions on a file so that it cannot be read by anyone other * than its owner. * * @param file file to alter * @return true if the attempt succeeded, false if it failed because * we are running the wrong version of java * @throws IOException if there was some I/O failure */ private static boolean setPrivateReadReflect( File file ) throws IOException { try { Method setReadableMethod = File.class.getMethod( "setReadable", new Class[] { boolean.class, boolean.class, } ); boolean success = ( setReadableMethod.invoke( file, new Object[] { Boolean.FALSE, Boolean.FALSE } ) .equals( Boolean.TRUE ) ) && ( setReadableMethod.invoke( file, new Object[] { Boolean.TRUE, Boolean.TRUE } ) .equals( Boolean.TRUE ) ); return success; } catch ( InvocationTargetException e1 ) { Throwable e2 = e1.getCause(); if ( e2 instanceof IOException ) { throw (IOException) e2; } else if ( e2 instanceof RuntimeException ) { throw (RuntimeException) e2; } else { throw (IOException) new IOException( e2.getMessage() ) .initCause( e2 ); } } catch ( NoSuchMethodException e ) { // method only available at java 1.6+ return false; } catch ( IllegalAccessException e ) { // not likely. return false; } } /** * Attempts a {@java.lang.Runtime#exec(java.lang.String[])} with a given * list of arguments. The output from stdout is returned as a string; * in the case of error an IOException is thrown with a message giving * the output from stderr. * *

Note: do not use this for cases in which the * output from stdout or stderr might be more than a few characters - * blocking or deadlock is possible (see {@link java.lang.Process}). * * @param args array of words to pass to exec * @return output from standard output * @throws IOException with text from standard error if there is an error */ private static String exec( String[] args ) throws IOException { String argv = Arrays.asList( args ).toString(); logger_.info( "System exec: " + argv ); final Process process; final StreamReader outReader; final StreamReader errReader; try { process = Runtime.getRuntime().exec( args ); outReader = new StreamReader( process.getInputStream() ); errReader = new StreamReader( process.getErrorStream() ); outReader.start(); errReader.start(); process.waitFor(); } catch ( InterruptedException e ) { throw new IOException( "Exec failed: " + argv ); } catch ( IOException e ) { throw (IOException) new IOException( "Exec failed: " + argv ).initCause( e ); } return process.exitValue() == 0 ? outReader.getContent() : errReader.getContent(); } /** * Returns a Platform instance for the current system. * * @return platform instance */ public static Platform getPlatform() { if ( instance_ == null ) { instance_ = createPlatform(); } return instance_; } /** * Constructs a Platform for the current system. * * @return new platform */ private static Platform createPlatform() { // Is this reliable? String osname = System.getProperty( "os.name" ); if ( osname.toLowerCase().startsWith( "windows" ) || osname.toLowerCase().indexOf( "microsoft" ) >= 0 ) { return new WindowsPlatform(); } else { return new UnixPlatform(); } } /** * Thread which reads the contents of a stream into a string buffer. */ private static class StreamReader extends Thread { private final InputStream in_; private final StringBuffer sbuf_; /** * Constructor. * * @param in input stream */ StreamReader( InputStream in ) { super( "StreamReader" ); in_ = in; sbuf_ = new StringBuffer(); setDaemon( true ); } public void run() { try { for ( int c; ( c = in_.read() ) >= 0; ) { sbuf_.append( (char) c ); } in_.close(); } catch ( IOException e ) { } } /** * Returns the content of the stream. * * @return content */ public String getContent() { return sbuf_.toString(); } } /** * Platform implementation for Un*x-like systems. */ private static class UnixPlatform extends Platform { /** * Constructor. */ UnixPlatform() { super( "Un*x" ); } public File getHomeDirectory() { return new File( System.getProperty( "user.home" ) ); } protected String[] getGetenvArgs( String varname ) { return new String[] { "printenv", varname, }; } protected String[] getPrivateReadArgs( File file ) { return new String[] { "chmod", "600", file.toString(), }; } } /** * Platform implementation for Microsoft Windows-like systems. */ private static class WindowsPlatform extends Platform { /** * Constructor. */ WindowsPlatform() { super( "MS Windows" ); } protected String[] getPrivateReadArgs( File file ) throws IOException { // No good way known. For a while I was using "attrib -R file", // but this wasn't doing what was wanted. Bruno Rino has // suggested "CALCS file /G %USERNAME%:F". Sounds kind of // sensible, but requires user input (doable, but fiddly), // and from my experiments on NTFS doesn't seem to have any // discernable effect. As I understand it, it's unlikely to do // anything on FAT (no ACLs). Given my general ignorance of // MS OSes and file systems, I'm inclined to leave this for // fear of inadvertently doing something bad. return null; } public File getHomeDirectory() { String userprofile = getEnv( "USERPROFILE" ); if ( userprofile != null && userprofile.trim().length() > 0 ) { return new File( userprofile ); } else { return new File( System.getProperty( "user.home" ) ); } } public String[] getGetenvArgs( String varname ) { return new String[] { "cmd", "/c", "echo", "%" + varname + "%", }; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/RegInfo.java000066400000000000000000000041771356450004300234060ustar00rootroot00000000000000package org.astrogrid.samp; import java.util.Map; /** * Represents information provided to a client at registration by the hub. * * @author Mark Taylor * @since 14 Jul 2008 */ public class RegInfo extends SampMap { /** Key for client public-id used by hub when sending messages itself. */ public static final String HUBID_KEY = "samp.hub-id"; /** Key for client public-id owned by the registering application. */ public static final String SELFID_KEY = "samp.self-id"; /** Key for private-key token used for communications between hub and * registering client (Standard Profile). */ public static final String PRIVATEKEY_KEY = "samp.private-key"; private static final String[] KNOWN_KEYS = new String[] { HUBID_KEY, SELFID_KEY, PRIVATEKEY_KEY, }; /** * Constructs an empty RegInfo. */ public RegInfo() { super( KNOWN_KEYS ); } /** * Constructs a RegInfo based on an existing map. * * @param map map containing initial data for this object */ public RegInfo( Map map ) { this(); putAll( map ); } /** * Returns the hub's own public client id. * * @return {@link #HUBID_KEY} value */ public String getHubId() { return getString( HUBID_KEY ); } /** * Returns the registered client's public client id. * * @return {@link #SELFID_KEY} value */ public String getSelfId() { return getString( SELFID_KEY ); } /** * Returns the registered client's private key (Standard Profile). * * @return {@link #PRIVATEKEY_KEY} value */ public String getPrivateKey() { return getString( PRIVATEKEY_KEY ); } public void check() { super.check(); checkHasKeys( new String[] { HUBID_KEY, SELFID_KEY, } ); } /** * Returns a given map as a RegInfo. * * @param map map * @return registration info */ public static RegInfo asRegInfo( Map map ) { return map instanceof RegInfo ? (RegInfo) map : new RegInfo( map ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Response.java000066400000000000000000000126331356450004300236470ustar00rootroot00000000000000package org.astrogrid.samp; import java.util.Map; /** * Represents an encoded SAMP response. * * @author Mark Taylor * @since 14 Jul 2008 */ public class Response extends SampMap { /** Key for response status. May take one of the values * {@link #OK_STATUS}, {@link #WARNING_STATUS} or {@link #ERROR_STATUS}. */ public static final String STATUS_KEY = "samp.status"; /** Key for result map. This is a map of key-value pairs with semantics * defined by the original message's MType. * Only present in case of success (or warning). */ public static final String RESULT_KEY = "samp.result"; /** Key for error map. Only present in case of failure (or warning). */ public static final String ERROR_KEY = "samp.error"; private static final String[] KNOWN_KEYS = new String[] { STATUS_KEY, RESULT_KEY, ERROR_KEY, }; /** {@link #STATUS_KEY} value indicating success. */ public static final String OK_STATUS = "samp.ok"; /** {@link #STATUS_KEY} value indicating partial success. */ public static final String WARNING_STATUS = "samp.warning"; /** {@link #STATUS_KEY} value indicating failure. */ public static final String ERROR_STATUS = "samp.error"; /** * Constructs an empty response. */ public Response() { super( KNOWN_KEYS ); } /** * Constructs a response based on an existing map. * * @param map map containing initial data for this object */ public Response( Map map ) { this(); putAll( map ); } /** * Constructs a response with given status, result and error. * * @param status {@link #STATUS_KEY} value * @param result {@link #RESULT_KEY} value * @param errinfo {@link #ERROR_KEY} value */ public Response( String status, Map result, ErrInfo errinfo ) { this(); put( STATUS_KEY, status ); if ( result != null ) { put( RESULT_KEY, result ); } if ( errinfo != null ) { put( ERROR_KEY, errinfo ); } } /** * Sets the status value. * * @param status {@link #STATUS_KEY} value */ public void setStatus( String status ) { put( STATUS_KEY, status ); } /** * Returns the status value. * * @return {@link #STATUS_KEY} value */ public String getStatus() { return getString( STATUS_KEY ); } /** * Sets the result map. * * @param result {@link #RESULT_KEY} value */ public void setResult( Map result ) { put( RESULT_KEY, result ); } /** * Returns the result map. * * @return {@link #RESULT_KEY} value */ public Map getResult() { return getMap( RESULT_KEY ); } /** * Sets the error object. * * @param errInfo {@link #ERROR_KEY} value * @see ErrInfo */ public void setErrInfo( Map errInfo ) { put( ERROR_KEY, errInfo ); } /** * Returns the error object. * * @return {@link #ERROR_KEY} value as an ErrInfo */ public ErrInfo getErrInfo() { return ErrInfo.asErrInfo( getMap( ERROR_KEY ) ); } /** * Indicates whether the result was an unequivocal success. * * @return true iff getStatus()==OK_STATUS */ public boolean isOK() { return OK_STATUS.equals( get( STATUS_KEY ) ); } public void check() { super.check(); checkHasKeys( new String[] { STATUS_KEY, } ); String status = getStatus(); if ( OK_STATUS.equals( status ) || WARNING_STATUS.equals( status ) ) { if ( ! containsKey( RESULT_KEY ) ) { throw new DataException( STATUS_KEY + "=" + status + " but no " + RESULT_KEY ); } } if ( ERROR_STATUS.equals( status ) || WARNING_STATUS.equals( status ) ) { if ( ! containsKey( ERROR_KEY ) ) { throw new DataException( STATUS_KEY + "=" + status + " but not " + ERROR_KEY ); } } if ( ! containsKey( RESULT_KEY ) && ! containsKey( ERROR_KEY ) ) { throw new DataException( "Neither " + RESULT_KEY + " nor " + ERROR_KEY + " keys present" ); } if ( containsKey( ERROR_KEY ) ) { ErrInfo.asErrInfo( getMap( ERROR_KEY ) ).check(); } } /** * Returns a new response which is a success. * * @param result key-value map representing results of successful call * @return new success response */ public static Response createSuccessResponse( Map result ) { return new Response( OK_STATUS, result, null ); } /** * Returns a new response which is an error. * * @param errinfo error information * @return new error response */ public static Response createErrorResponse( ErrInfo errinfo ) { return new Response( ERROR_STATUS, null, errinfo ); } /** * Returns a map as a Response object. * * @param map map * @return response */ public static Response asResponse( Map map ) { return ( map instanceof Response || map == null ) ? (Response) map : new Response( map ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/SampMap.java000066400000000000000000000140631356450004300234060ustar00rootroot00000000000000package org.astrogrid.samp; import java.net.MalformedURLException; import java.net.URL; import java.util.AbstractMap; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; /** * Abstract superclass for objects represented within the SAMP package as * key-value maps. There are several of these, represented by subclasses * of SampMap, for instance {@link Message}, {@link Metadata} etc. * A SampMap is-a {@link java.util.Map}, but has some * additional useful features: *

    *
  • its entries are ordered in a convenient fashion *
  • it contains some general-purpose utility methods appropriate to SAMP maps *
  • particular subclasses contain specific constants and utility methods, * e.g. well-known keys and accessor methods for them *
  • concrete subclasses provide a static asClass * method to convert from a normal Map to the class in question *
* *

In general * any time a map-encoded object is required by a method in the toolkit, * any Map can be used. When the toolkit provides a map-encoded * object however (as return value or callback method parameter), an object * of the more specific SampMap type is used. * This allows maximum convenience for the application programmer, but * means that you don't have to use these additional features if you * don't want to, you can treat everything as a plain old Map. * * @author Mark Taylor * @since 14 Jul 2008 */ public abstract class SampMap extends AbstractMap { private final Map baseMap_; public static final Map EMPTY = Collections.unmodifiableMap( new HashMap() ); /** * Constructor. * The given array of well-known keys will appear first in the list of * entries when this map is iterated over. Other entries will appear in * alphabetical order. * * @param knownKeys array of well-known keys for this class */ protected SampMap( String[] knownKeys ) { super(); final List knownKeyList = Arrays.asList( (String[]) knownKeys.clone() ); baseMap_ = new TreeMap( new Comparator() { public int compare( Object o1, Object o2 ) { String s1 = o1.toString(); String s2 = o2.toString(); int k1 = knownKeyList.indexOf( s1 ); int k2 = knownKeyList.indexOf( s2 ); if ( k1 >= 0 ) { if ( k2 >= 0 ) { return k1 - k2; } else { return -1; } } else if ( k2 >= 0 ) { assert k1 < 0; return +1; } boolean f1 = s1.startsWith( "samp." ); boolean f2 = s2.startsWith( "samp." ); if ( f1 && ! f2 ) { return -1; } else if ( ! f1 && f2 ) { return +1; } boolean g1 = s1.startsWith( "x-samp." ); boolean g2 = s2.startsWith( "x-samp." ); if ( g1 && ! g2 ) { return -1; } else if ( ! g1 && g2 ) { return +1; } return s1.compareTo( s2 ); } } ); } public Object put( Object key, Object value ) { return baseMap_.put( key, value ); } public Set entrySet() { return baseMap_.entrySet(); } /** * Checks that this object is ready for use with the SAMP toolkit. * As well as calling {@link SampUtils#checkMap} (ensuring that all keys * are Strings, and all values Strings, Lists or Maps), subclass-specific * invariants may be checked. In the case that there's something wrong, * an informative DataException will be thrown. * * @throws DataException if this object's current state * is not suitable for SAMP use */ public void check() { SampUtils.checkMap( this ); } /** * Checks that this map contains at least the given set of keys. * If any is absent, an informative DataException will be * thrown. Normally called by {@link #check}. * * @param keys array of required keys for this map * @throws DataException if this object does not contain entries * for all elements of the array keys */ public void checkHasKeys( String[] keys ) { for ( int i = 0; i < keys.length; i++ ) { String key = keys[ i ]; if ( ! containsKey( key ) ) { throw new DataException( "Required key " + key + " not present" ); } } } /** * Returns the value for a given key in this map, cast to String. * * @return string value for key */ public String getString( String key ) { return (String) get( key ); } /** * returns the value for a given key in this map, cast to Map. * * @return map value for key */ public Map getMap( String key ) { return (Map) get( key ); } /** * Returns the value for a given key in this map, cast to List. * * @return list value for key */ public List getList( String key ) { return (List) get( key ); } /** * Returns the value for a given key in this map as a URL. * * @return URL value for key */ public URL getUrl( String key ) { String loc = getString( key ); if ( loc == null ) { return null; } else { try { return new URL( loc ); } catch ( MalformedURLException e ) { return null; } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/SampUtils.java000066400000000000000000000531051356450004300237710ustar00rootroot00000000000000package org.astrogrid.samp; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.ConnectException; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * Contains static utility methods for use with the SAMP toolkit. * * @author Mark Taylor * @since 15 Jul 2008 */ public class SampUtils { /** * Property which can be used to set name used for localhost in server * endpoints. * Value is {@value}. * @see #getLocalhost */ public static final String LOCALHOST_PROP = "jsamp.localhost"; private static final Logger logger_ = Logger.getLogger( SampUtils.class.getName() ); private static String sampVersion_; private static String softwareVersion_; private static File lockFile_; private static final String NEWLINE = getLineSeparator(); /** * Private constructor prevents instantiation. */ private SampUtils() { } /** * Returns a SAMP int string representation of an integer. * * @param i integer value * @return SAMP int string */ public static String encodeInt( int i ) { return Integer.toString( i ); } /** * Returns the integer value for a SAMP int string. * * @param s SAMP int string * @return integer value * @throws NumberFormatException if conversion fails */ public static int decodeInt( String s ) { return Integer.parseInt( s ); } /** * Returns a SAMP int string representation of a long integer. * * @param i integer value * @return SAMP int string */ public static String encodeLong( long i ) { return Long.toString( i ); } /** * Returns the integer value as a long for a SAMP int * string. * * @param s SAMP int string * @return long integer value * @throws NumberFormatException if conversion fails */ public static long decodeLong( String s ) { return Long.parseLong( s ); } /** * Returns a SAMP float string representation of a floating point * value. * * @param d double value * @return SAMP double string * @throws IllegalArgumentException if d is NaN or infinite */ public static String encodeFloat( double d ) { if ( Double.isInfinite( d ) ) { throw new IllegalArgumentException( "Infinite value " + "not permitted" ); } if ( Double.isNaN( d ) ) { throw new IllegalArgumentException( "NaN not permitted" ); } return Double.toString( d ); } /** * Returns the double value for a SAMP float string. * * @param s SAMP float string * @return double value * @throws NumberFormatException if conversion fails */ public static double decodeFloat( String s ) { return Double.parseDouble( s ); } /** * Returns a SAMP boolean string representation of a boolean value. * * @param b boolean value * @return SAMP boolean string */ public static String encodeBoolean( boolean b ) { return encodeInt( b ? 1 : 0 ); } /** * Returns the boolean value for a SAMP boolean string. * * @param s SAMP boolean string * @return false iff s is equal to zero */ public static boolean decodeBoolean( String s ) { try { return decodeInt( s ) != 0; } catch ( NumberFormatException e ) { return false; } } /** * Checks that a given object is legal for use in a SAMP context. * This checks that it is either a String, List or Map, that * any Map keys are Strings, and that Map values and List elements are * themselves legal (recursively). * * @param obj object to check * @throws DataException in case of an error */ public static void checkObject( Object obj ) { if ( obj instanceof Map ) { checkMap( (Map) obj ); } else if ( obj instanceof List ) { checkList( (List) obj ); } else if ( obj instanceof String ) { checkString( (String) obj ); } else if ( obj == null ) { throw new DataException( "Bad SAMP object: contains a null" ); } else { throw new DataException( "Bad SAMP object: contains a " + obj.getClass().getName() ); } } /** * Checks that a given Map is legal for use in a SAMP context. * All its keys must be strings, and its values must be legal * SAMP objects. * * @param map map to check * @throws DataException in case of an error * @see #checkObject */ public static void checkMap( Map map ) { for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); Object key = entry.getKey(); if ( key instanceof String ) { checkString( (String) key ); checkObject( entry.getValue() ); } else if ( key == null ) { throw new DataException( "Map key null" ); } else { throw new DataException( "Map key not a string (" + key.getClass().getName() + ")" ); } } } /** * Checks that a given List is legal for use in a SAMP context. * All its elements must be legal SAMP objects. * * @param list list to check * @throws DataException in case of error * @see #checkObject */ public static void checkList( List list ) { for ( Iterator it = list.iterator(); it.hasNext(); ) { checkObject( it.next() ); } } /** * Checks that a given String is legal for use in a SAMP context. * All its characters must be in the range 0x01 - 0x7f. * * @param string string to check * @throws DataException in case of error */ public static void checkString( String string ) { int leng = string.length(); for ( int i = 0; i < leng; i++ ) { char c = string.charAt( i ); if ( ! isStringChar( c ) ) { throw new DataException( "Bad SAMP string; contains character " + "0x" + Integer.toHexString( c ) ); } } } /** * Indicates whether a given character is legal to include in a SAMP * string. * * @return true iff c is 0x09, 0x0a, 0x0d or 0x20--0x7f */ public static boolean isStringChar( char c ) { switch ( c ) { case 0x09: case 0x0a: case 0x0d: return true; default: return c >= 0x20 && c <= 0x7f; } } /** * Checks that a string is a legal URL. * * @param url string to check * @throws DataException if url is not a legal URL */ public static void checkUrl( String url ) { if ( url != null ) { try { new URL( (String) url ); } catch ( MalformedURLException e ) { throw new DataException( "Bad URL " + url, e ); } } } /** * Returns a string representation of a client object. * The name is used if present, otherwise the ID. * * @param client client object * @return string */ public static String toString( Client client ) { Metadata meta = client.getMetadata(); if ( meta != null ) { String name = meta.getName(); if ( name != null && name.trim().length() > 0 ) { return name; } } return client.getId(); } /** * Pretty-prints a SAMP object. * * @param obj SAMP-friendly object * @param indent base indent for text block * @return string containing formatted object */ public static String formatObject( Object obj, int indent ) { checkObject( obj ); return new JsonWriter( indent, true ).toJson( obj ); } /** * Parses a command-line string as a SAMP object. * If it can be parsed as a SAMP-friendly JSON string, that interpretation * will be used. Otherwise, the value is just the string as presented. * * @param str command-line argument * @return SAMP object */ public static Object parseValue( String str ) { if ( str == null || str.length() == 0 ) { return null; } else { try { Object obj = fromJson( str ); checkObject( obj ); return obj; } catch ( RuntimeException e ) { logger_.config( "String not JSON (" + e + ")" ); } Object sval = str; checkObject( sval ); return sval; } } /** * Returns a string denoting the local host to be used for communicating * local server endpoints and so on. * *

The value returned by default is the loopback address, "127.0.0.1". * However this behaviour can be overridden by setting the * {@link #LOCALHOST_PROP} system property to the string which should * be returned instead. * This may be necessary if the loopback address is not appropriate, * for instance in the case of multiple configured loopback interfaces(?) * or where SAMP communication is required across different machines. * There are two special values which may be used for this property: *

    *
  • [hostname]: * uses the fully qualified domain name of the host
  • *
  • [hostnumber]: * uses the IP number of the host
  • *
* If these determinations fail for some reason, a fallback value of * 127.0.0.1 will be used. * *

In JSAMP version 0.3-1 and prior versions, the [hostname] * behaviour was the default. * Although this might be seen as more correct, in practice it could cause * a lot of problems with DNS configurations which are incorrect or * unstable (common in laptops outside their usual networks). * See, for instance, AstroGrid bugzilla tickets * 1799, * 2151. * *

In JSAMP version 0.3-1 and prior versions, the property was * named samp.localhost rather than * jsamp.localhost. This name is still accepted for * backwards compatibility. * * @return local host name */ public static String getLocalhost() { final String defaultHost = "127.0.0.1"; String hostname = System.getProperty( LOCALHOST_PROP, System.getProperty( "samp.localhost", "" ) ); if ( hostname.length() == 0 ) { hostname = defaultHost; } else if ( "[hostname]".equals( hostname ) ) { try { hostname = InetAddress.getLocalHost().getCanonicalHostName(); } catch ( UnknownHostException e ) { logger_.log( Level.WARNING, "Local host determination failed - fall back to " + defaultHost, e ); hostname = defaultHost; } } else if ( "[hostnumber]".equals( hostname ) ) { try { hostname = InetAddress.getLocalHost().getHostAddress(); } catch ( UnknownHostException e ) { logger_.log( Level.WARNING, "Local host determination failed - fall back to " + defaultHost, e ); hostname = defaultHost; } } logger_.config( "Local host is " + hostname ); return hostname; } /** * Returns an unused port number on the local host. * * @param startPort suggested port number; may or may not be used * @return unused port */ public static int getUnusedPort( int startPort ) throws IOException { // Current implementation ignores the given startPort and uses // findAnyPort. return true ? findAnyPort() : scanForPort( startPort, 20 ); } /** * Turns a File into a URL. * Unlike Sun's J2SE, this gives you a URL which conforms to RFC1738 and * looks like "file://localhost/abs-path" rather than * "file:abs-or-rel-path". * For non-ASCII characters, UTF-8 encoding is used. * * @param file file * @return URL * @see "RFC 1738" * @see Sun Java bug 6356783 */ public static URL fileToUrl( File file ) { try { String path = file.toURI().toURL().getPath(); StringBuffer pbuf = new StringBuffer(); for ( int ic = 0; ic < path.length(); ic++ ) { char c = path.charAt( ic ); if ( c == '/' ) { pbuf.append( c ); } else { pbuf.append( uriEncode( new String( new char[] { c } ) ) ); } } return new URL( "file", "localhost", pbuf.toString() ); } catch ( MalformedURLException e ) { throw new AssertionError(); } } /** * Reverses URI-style character escaping (%xy) on a string. * Note, unlike {@link java.net.URLDecoder}, * this does not turn "+" characters into spaces. * For non-ASCII characters, UTF-8 encoding is used. * * @see "RFC 2396, Section 2.4" * @param text escaped text * @return unescaped text */ public static String uriDecode( String text ) { try { return URLDecoder.decode( replaceChar( text, '+', "%2B" ), "UTF-8" ); } catch ( UnsupportedEncodingException e ) { throw new AssertionError( "UTF-8 unsupported??" ); } } /** * Performs URI-style character escaping (%xy) on a string. * Note, unlike {@link java.net.URLEncoder}, * this encodes spaces as "%20" and not "+". * For non-ASCII characters, UTF-8 encoding is used. * * @see "RFC 2396, Section 2.4" * @param text unescaped text * @return escaped text */ public static String uriEncode( String text ) { try { return replaceChar( URLEncoder.encode( text, "UTF-8" ), '+', "%20" ); } catch ( UnsupportedEncodingException e ) { throw new AssertionError( "UTF-8 unsupported??" ); } } /** * Attempts to interpret a URL as a file. * If the URL does not have the "file:" protocol, null is returned. * * @param url URL, may or may not be file: protocol * @return file, or null */ public static File urlToFile( URL url ) { if ( url.getProtocol().equals( "file" ) && url.getRef() == null && url.getQuery() == null ) { String path = uriDecode( url.getPath() ); String filename = File.separatorChar == '/' ? path : path.replace( '/', File.separatorChar ); return new File( filename ); } else { return null; } } /** * Parses JSON text to give a SAMP object. * Note that double-quoted strings are the only legal scalars * (no unquoted numbers or booleans). * * @param str string to parse * @return SAMP object */ public static Object fromJson( String str ) { return new JsonReader().read( str ); } /** * Serializes a SAMP object to a JSON string. * * @param item to serialize * @param multiline true for formatted multiline output, false for a * single line */ public static String toJson( Object item, boolean multiline ) { checkObject( item ); return new JsonWriter( multiline ? 2 : -1, true ).toJson( item ); } /** * Returns a string giving the version of the SAMP standard which this * software implements. * * @return SAMP standard version */ public static String getSampVersion() { if ( sampVersion_ == null ) { sampVersion_ = readResource( "samp.version" ); } return sampVersion_; } /** * Returns a string giving the version of this software package. * * @return JSAMP version */ public static String getSoftwareVersion() { if ( softwareVersion_ == null ) { softwareVersion_ = readResource( "jsamp.version" ); } return softwareVersion_; } /** * Returns the contents of a resource as a string. * * @param rname resource name * (in the sense of {@link java.lang.Class#getResource}) */ private static String readResource( String rname ) { URL url = SampUtils.class.getResource( rname ); if ( url == null ) { logger_.warning( "No such resource " + rname ); return "??"; } else { try { InputStream in = url.openStream(); StringBuffer sbuf = new StringBuffer(); for ( int c; ( c = in.read() ) >= 0; ) { sbuf.append( (char) c ); } in.close(); return sbuf.toString().trim(); } catch ( IOException e ) { logger_.warning( "Failed to read resource " + url ); return "??"; } } } /** * Returns the system-dependent line separator sequence. * * @return line separator */ private static String getLineSeparator() { try { return System.getProperty( "line.separator", "\n" ); } catch ( SecurityException e ) { return "\n"; } } /** * Replaces all occurrences of a single character with a given replacement * string. * * @param in input string * @param oldChar character to replace * @param newText replacement string * @return modified string */ private static String replaceChar( String in, char oldChar, String newTxt ) { int len = in.length(); StringBuffer sbuf = new StringBuffer( len ); for ( int i = 0; i < len; i++ ) { char c = in.charAt( i ); if ( c == oldChar ) { sbuf.append( newTxt ); } else { sbuf.append( c ); } } return sbuf.toString(); } /** * Locates an unused server port on the local host. * Potential problem: between when this method completes and when * the return value of this method is used by its caller, it's possible * that the port will get used by somebody else. * Probably this will not happen much in practice?? * * @return unused server port */ private static int findAnyPort() throws IOException { ServerSocket socket = new ServerSocket( 0 ); try { return socket.getLocalPort(); } finally { try { socket.close(); } catch ( IOException e ) { } } } /** * Two problems with this one - it may be a bit inefficient, and * there's an annoying bug in the Apache XML-RPC WebServer class * which causes it to print "java.util.NoSuchElementException" to * the server's System.err for every port scanned by this routine * that an org.apache.xmlrpc.WebServer server is listening on. * * @param startPort port to start scanning upwards from * @param nTry number of ports in sequence to try before admitting defeat * @return unused server port */ private static int scanForPort( int startPort, int nTry ) throws IOException { for ( int iPort = startPort; iPort < startPort + nTry; iPort++ ) { try { Socket trySocket = new Socket( "localhost", iPort ); if ( ! trySocket.isClosed() ) { trySocket.shutdownOutput(); trySocket.shutdownInput(); trySocket.close(); } } catch ( ConnectException e ) { /* Can't connect - this hopefully means that the socket is * unused. */ return iPort; } } throw new IOException( "Can't locate an unused port in range " + startPort + " ... " + ( startPort + nTry ) ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/ShutdownManager.java000066400000000000000000000117121356450004300251540ustar00rootroot00000000000000package org.astrogrid.samp; import java.util.Arrays; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * Handles ordered running of cleanup processes at JVM shutdown. * This is a singleton class, use {@link #getInstance}. * * @author Sylvain Lafrasse * @author Mark Taylor * @since 12 Oct 2012 */ public class ShutdownManager { /** Shutdown sequence for preparatory hooks. */ public static final int PREPARE_SEQUENCE = 0; /** Shutdown sequence value for client hooks. */ public static final int CLIENT_SEQUENCE = 100; /** Shutdown sequence value for hub hooks. */ public static final int HUB_SEQUENCE = 200; private static final ShutdownManager instance_ = new ShutdownManager(); private static final Logger logger_ = Logger.getLogger( "org.astrogrid.samp" ); /** Maps Objects to Hooks. */ private final WeakHashMap hookMap_; /** * Private constructor prevents instantiation. */ private ShutdownManager() { hookMap_ = new WeakHashMap(); try { Runtime.getRuntime() .addShutdownHook( new Thread( "SAMP Shutdown" ) { public void run() { doCleanup(); } } ); } catch ( SecurityException e ) { logger_.log( Level.WARNING, "Can't add shutdown hook: " + e, e ); } } /** * Register a runnable to be run on shutdown with a given key and sequence. * Items with a smaller value of iseq * are run earlier at shutdown. * Suitable sequence values are given by {@link #HUB_SEQUENCE} and * {@link #CLIENT_SEQUENCE}. * The key is kept in a WeakHashMap, so if it is GC'd, * the runnable will never execute. * * @param key key which can be used to unregister the hook later * @param iseq value indicating position in shutdown sequence * @param runnable to be run on shutdown */ public synchronized void registerHook( Object key, int iseq, Runnable runnable ) { hookMap_.put( key, new Hook( runnable, iseq ) ); } /** * Unregisters a key earlier registered using {@link #registerHook}. * * @param key registration key */ public synchronized void unregisterHook( Object key ) { hookMap_.remove( key ); } /** * Invoked on shutdown by runtime. */ private void doCleanup() { Hook[] hooks; synchronized ( this ) { hooks = (Hook[]) hookMap_.values().toArray( new Hook[ 0 ] ); } Arrays.sort( hooks ); logger_.info( "SAMP shutdown start" ); for ( int ih = 0; ih < hooks.length; ih++ ) { try { hooks[ ih ].runnable_.run(); } catch ( RuntimeException e ) { forceLog( logger_, Level.WARNING, "Shutdown hook failure: " + e, e ); } } logger_.info( "SAMP shutdown end" ); } /** * Returns sole instance of this class. * * @return instance */ public static ShutdownManager getInstance() { return instance_; } /** * Writes a log-like message directly to standard error if it has * an appropriate level. * This method is only intended for use during the shutdown process, * when the logging system may be turned off so that normal logging * calls may get ignored (this behaviour is not as far as I know * documented, but seems reliable in for example Oracle JRE1.5). * There may be some good reason for logging services to be withdrawn * during shutdown, so it's not clear that using this method is * a good idea at all even apart from bypassing the logging system; * therefore use it sparingly. * * @param logger logger * @param level level of message to log * @param msg text of logging message * @param error associated throwable if any; may be null */ public static void forceLog( Logger logger, Level level, String msg, Throwable error ) { if ( logger.isLoggable( level ) ) { System.err.println( level + ": " + msg ); if ( error != null ) { error.printStackTrace( System.err ); } } } /** * Aggregates a runnable and an associated sequence value. */ private static class Hook implements Comparable { final Runnable runnable_; final int iseq_; /** * Constructor. * * @param runnable runnable * @param iseq sequence value */ Hook( Runnable runnable, int iseq ) { runnable_ = runnable; iseq_ = iseq; } public int compareTo( Object other ) { return this.iseq_ - ((Hook) other).iseq_; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/Subscriptions.java000066400000000000000000000133121356450004300247130ustar00rootroot00000000000000package org.astrogrid.samp; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Represents the set of subscribed messages for a SAMP client. * This has the form of a Map in which each key is an MType (perhaps * wildcarded) and the corresponding values are maps with keys which are * so far undefined (thus typically empty). * * @author Mark Taylor * @since 14 Jul 2008 */ public class Subscriptions extends SampMap { private static final String ATOM_REGEX = "[0-9a-zA-Z\\-_]+"; private static String MTYPE_REGEX = "(" + ATOM_REGEX + "\\.)*" + ATOM_REGEX; private static String MSUB_REGEX = "(" + MTYPE_REGEX + "|" + MTYPE_REGEX + "\\.\\*" + "|" + "\\*" + ")"; private static final Pattern MSUB_PATTERN = Pattern.compile( MSUB_REGEX ); /** * Constructs an empty subscriptions object. */ public Subscriptions() { super( new String[ 0 ] ); } /** * Constructs a subscriptions object based on an existing map. * * @param map map containing initial data for this object */ public Subscriptions( Map map ) { this(); putAll( map ); } /** * Adds a subscription to a given MType. mtype may include * a wildcard according to the SAMP rules. * * @param mtype subscribed MType, possibly wildcarded */ public void addMType( String mtype ) { put( mtype, new HashMap() ); } /** * Determines whether a given (non-wildcarded) MType is subscribed to * by this object. * * @param mtype MType to test */ public boolean isSubscribed( String mtype ) { if ( containsKey( mtype ) ) { return true; } for ( Iterator it = keySet().iterator(); it.hasNext(); ) { if ( matchLevel( (String) it.next(), mtype ) >= 0 ) { return true; } } return false; } /** * Returns the map which forms the value for a given MType key. * If a wildcarded subscription is recorded which matches * mtype, the corresponding value is returned. * If mtype is not subscribed to, null * is returned. * * @param mtype MType to query * @return map value corresponding to mtype, or null */ public Map getSubscription( String mtype ) { if ( containsKey( mtype ) ) { Object value = get( mtype ); return value instanceof Map ? (Map) value : (Map) new HashMap(); } else { int bestLevel = -1; Map bestValue = null; for ( Iterator it = entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); int level = matchLevel( (String) entry.getKey(), mtype ); if ( level > bestLevel ) { bestLevel = level; Object value = entry.getValue(); bestValue = value instanceof Map ? (Map) value : (Map) new HashMap(); } } return bestValue; } } public void check() { super.check(); for ( Iterator it = entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); String key = (String) entry.getKey(); Object value = entry.getValue(); if ( ! MSUB_PATTERN.matcher( key ).matches() ) { throw new DataException( "Illegal subscription key " + "\"" + key + "\"" ); } if ( ! ( value instanceof Map ) ) { throw new DataException( "Subscription values " + "are not all maps" ); } } } /** * Returns a given map in the form of a Subscriptions object. * * @param map map * @return subscriptions */ public static Subscriptions asSubscriptions( Map map ) { return ( map instanceof Subscriptions || map == null ) ? (Subscriptions) map : new Subscriptions( map ); } /** * Performs wildcard matching of MTypes. The result is the number of * dot-separated "atoms" which match between the two. * * @param pattern MType pattern; may contain a wildcard * @param mtype unwildcarded MType for comparison with * pattern * @return the number of atoms of pattern which match * mtype; if pattern="*" the result is * 0, and if there is no match the result is -1 */ public static int matchLevel( String pattern, String mtype ) { if ( mtype.equals( pattern ) ) { return countAtoms( pattern ); } else if ( "*".equals( pattern ) ) { return 0; } else if ( pattern.endsWith( ".*" ) ) { String prefix = pattern.substring( 0, pattern.length() - 2 ); return mtype.startsWith( prefix ) ? countAtoms( prefix ) : -1; } else { return -1; } } /** * Counts the number of dot-separated "atoms" in a string. * * @param text string to test */ private static int countAtoms( String text ) { int leng = text.length(); int natom = 1; for ( int i = 0; i < leng; i++ ) { if ( text.charAt( i ) == '.' ) { natom++; } } return natom; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/000077500000000000000000000000001356450004300224355ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/Bridge.java000066400000000000000000000441011356450004300244740ustar00rootroot00000000000000package org.astrogrid.samp.bridge; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.HubConnector; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.UtilServer; import org.astrogrid.samp.xmlrpc.LockInfo; import org.astrogrid.samp.xmlrpc.StandardClientProfile; import org.astrogrid.samp.xmlrpc.XmlRpcKit; /** * Runs a bridging service between two or more hubs. * For each client on one hub, a proxy client appears on all other * participating hubs. These proxies can be treated in exactly the * same way as normal clients by other registered clients; any * messages sent to/from them will be marshalled over the bridge * in a transparent way. One application for this is to allow * collaboration between users who each have their own hub running. * *

A {@link java.lang.Object#notifyAll notifyAll} call is made on * the Bridge object whenever the number of live hubs connected by * the bridge changes. * * @author Mark Taylor * @since 15 Jul 2009 */ public class Bridge { private final ProxyManager[] proxyManagers_; private static final Logger logger_ = Logger.getLogger( Bridge.class.getName() ); /** * Constructor. * * @param profiles array of SAMP profile objects, one for each * hub which is to participate in the bridge */ public Bridge( ClientProfile[] profiles ) throws IOException { int nhub = profiles.length; proxyManagers_ = new ProxyManager[ nhub ]; UtilServer server = UtilServer.getInstance(); for ( int ih = 0; ih < nhub; ih++ ) { proxyManagers_[ ih ] = new ProxyManager( profiles[ ih ], server ) { protected void managerConnectionChanged( boolean isConnected ) { super.managerConnectionChanged( isConnected ); synchronized ( Bridge.this ) { Bridge.this.notifyAll(); } } }; } for ( int ih = 0; ih < nhub; ih++ ) { proxyManagers_[ ih ].init( proxyManagers_ ); } for ( int ih = 0; ih < nhub; ih++ ) { proxyManagers_[ ih ].getManagerConnector().setAutoconnect( 0 ); } } /** * Returns the client profiles which define the hubs this bridge links. * * @return profile array, one for each connected hub */ public ClientProfile[] getProfiles() { int nhub = proxyManagers_.length; ClientProfile[] profiles = new ClientProfile[ nhub ]; for ( int ih = 0; ih < nhub; ih++ ) { profiles[ ih ] = proxyManagers_[ ih ].getProfile(); } return profiles; } /** * Returns the hub connectors representing the bridge client running * on each linked hub. Note this does not include any proxy clients, * only the one-per-hub manager clients. * * @return array of bridge manager clients, one for each hub * (in corresponding positions to the profiles) */ public HubConnector[] getBridgeClients() { int nhub = proxyManagers_.length; HubConnector[] connectors = new HubConnector[ nhub ]; for ( int ih = 0; ih < nhub; ih++ ) { connectors[ ih ] = proxyManagers_[ ih ].getManagerConnector(); } return connectors; } /** * Sets up a URL exporter for one of the hubs. This will attempt to * edit transmitted data contents for use in remote contexts; * the main job is to adjust loopback host references in URLs * (127.0.0.1 or localhost) to become fully qualified domain names * for non-local use. It's not an exact science, but a best effort * is made. * * @param index index of the profile for which to export URLs * @param host the name substitute for loopback host identifiers * on the host on which that profile's hub is running */ public void exportUrls( int index, String host ) { proxyManagers_[ index ] .setExporter( new UrlExporter( host, isLocalHost( host ) ) ); } /** * Starts this bridge running. * * @return true iff all the participating hubs have been contacted * successfully */ public boolean start() { HubConnector[] connectors = getBridgeClients(); boolean allConnected = true; for ( int ih = 0; ih < connectors.length; ih++ ) { HubConnector connector = connectors[ ih ]; connector.setActive( true ); allConnected = allConnected && connector.isConnected(); } return allConnected; } /** * Stops this bridge running. * All associated manager and proxy clients are unregistered. */ public void stop() { HubConnector[] connectors = getBridgeClients(); for ( int ih = 0; ih < connectors.length; ih++ ) { connectors[ ih ].setActive( false ); } } /** * Returns the number of hubs currently connected by this bridge. * Only connections which are currently live are counted. * * @return number of live hubs */ private int getConnectionCount() { HubConnector[] connectors = getBridgeClients(); int nc = 0; for ( int ih = 0; ih < connectors.length; ih++ ) { if ( connectors[ ih ].isConnected() ) { nc++; } } return nc; } /** * Indicates whether a given hostname corresponds to the local host. * * @param host hostname to test * @return true if host is known to be the local host */ private static boolean isLocalHost( String host ) { if ( host == null ) { return false; } if ( SampUtils.getLocalhost().equals( host ) ) { return true; } try { InetAddress hostAddr = InetAddress.getByName( host ); return hostAddr != null && ( hostAddr.isLoopbackAddress() || hostAddr.equals( InetAddress.getLocalHost() ) ); } catch ( UnknownHostException e ) { return false; } } /** * Main method. Runs a bridge. */ public static void main( String[] args ) throws IOException { // Unless specially requested, make sure that the local host // is referred to by something publicly useful, not the loopback // address, which would be no good if there will be communications // to/from an external host. String hostspec = System.getProperty( SampUtils.LOCALHOST_PROP ); if ( hostspec == null ) { System.setProperty( SampUtils.LOCALHOST_PROP, "[hostname]" ); } // Run the application. int status = runMain( args ); if ( status != 0 ) { System.exit( status ); } } /** * Does the work for the main method. * Use -help flag. */ public static int runMain( String[] args ) throws IOException { String usage = new StringBuffer() .append( "\n Usage:" ) .append( "\n " ) .append( Bridge.class.getName() ) .append( "\n " ) .append( " [-help]" ) .append( " [-/+verbose]" ) .append( " [-[no]exporturls]" ) .append( "\n " ) .append( " [-nostandard]" ) .append( " [-sampdir ]" ) .append( " [-sampfile ]" ) .append( " [-sampurl ]" ) .append( "\n " ) .append( " [-keys ]" ) .append( " [-profile ]" ) .append( "\n" ) .toString(); List argList = new ArrayList( Arrays.asList( args ) ); // Handle administrative flags - best done before other parameters. int verbAdjust = 0; for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.equals( "-v" ) || arg.equals( "-verbose" ) ) { it.remove(); verbAdjust--; } else if ( arg.equals( "+v" ) || arg.equals( "+verbose" ) ) { it.remove(); verbAdjust++; } else if ( arg.equals( "-h" ) || arg.equals( "-help" ) || arg.equals( "--help" ) ) { it.remove(); System.out.println( usage ); return 0; } } // Adjust logging in accordance with verbosity flags. int logLevel = Level.WARNING.intValue() + 100 * verbAdjust; Logger.getLogger( "org.astrogrid.samp" ) .setLevel( Level.parse( Integer.toString( logLevel ) ) ); // Assemble list of profiles to use from command line arguments. List profileList = new ArrayList(); XmlRpcKit xmlrpcKit = XmlRpcKit.getInstance(); ClientProfile standardProfile = new ClientProfile() { public boolean isHubRunning() { return StandardClientProfile.getInstance().isHubRunning(); } public HubConnection register() throws SampException { return StandardClientProfile.getInstance().register(); } public String toString() { return "standard"; } }; profileList.add( standardProfile ); Boolean reqExportUrls = null; for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); // Determine whether to export localhost-type URLs. if ( arg.equals( "-exporturls" ) ) { reqExportUrls = Boolean.TRUE; } else if ( arg.equals( "-noexporturls" ) ) { reqExportUrls = Boolean.FALSE; } // Accumulate various profiles. else if ( arg.equals( "-standard" ) ) { it.remove(); profileList.remove( standardProfile ); profileList.add( standardProfile ); } else if ( arg.equals( "-nostandard" ) ) { it.remove(); profileList.remove( standardProfile ); } else if ( arg.equals( "-sampfile" ) && it.hasNext() ) { it.remove(); String fname = (String) it.next(); it.remove(); final File lockfile = new File( fname ); profileList.add( new StandardClientProfile( xmlrpcKit ) { public LockInfo getLockInfo() throws IOException { return LockInfo .readLockFile( SampUtils.fileToUrl( lockfile ) ); } public String toString() { return lockfile.toString(); } } ); } else if ( arg.equals( "-sampdir" ) && it.hasNext() ) { it.remove(); final String dirname = (String) it.next(); it.remove(); final File lockfile = new File( dirname, StandardClientProfile.LOCKFILE_NAME ); profileList.add( new StandardClientProfile( xmlrpcKit ) { public LockInfo getLockInfo() throws IOException { return LockInfo .readLockFile( SampUtils.fileToUrl( lockfile ) ); } public String toString() { return dirname; } } ); } else if ( arg.equals( "-sampurl" ) && it.hasNext() ) { it.remove(); final URL lockUrl = new URL( (String) it.next() ); it.remove(); profileList.add( new StandardClientProfile( xmlrpcKit ) { public LockInfo getLockInfo() throws IOException { return LockInfo.readLockFile( lockUrl ); } public String toString() { return lockUrl.toString(); } } ); } else if ( arg.equals( "-keys" ) && it.hasNext() ) { it.remove(); String endpoint = (String) it.next(); final URL url; try { url = new URL( endpoint ); } catch ( MalformedURLException e ) { System.err.println( "Not a URL: " + endpoint ); System.err.println( usage ); return 1; } it.remove(); if ( ! it.hasNext() ) { System.err.println( usage ); return 1; } final String secret = (String) it.next(); it.remove(); profileList.add( new StandardClientProfile( xmlrpcKit ) { public LockInfo getLockInfo() throws IOException { return new LockInfo( secret, url.toString() ); } public String toString() { return url.toString(); } } ); } else if ( arg.equals( "-profile" ) && it.hasNext() ) { it.remove(); String cname = (String) it.next(); it.remove(); final ClientProfile profile; try { profile = (ClientProfile) Class.forName( cname ).newInstance(); } catch ( Exception e ) { System.err.println( "Error instantiating class " + cname + "; " + e ); System.err.println( usage ); return 1; } profileList.add( profile ); } else { it.remove(); System.err.println( usage ); return 1; } } assert argList.isEmpty(); // Get the array of profiles to bridge between. ClientProfile[] profiles = (ClientProfile[]) profileList.toArray( new ClientProfile[ 0 ] ); if ( profiles.length < 2 ) { System.err.println( ( profiles.length == 0 ? "No" : "Only one" ) + " hub specified - no bridging to be done" ); if ( args.length == 0 ) { System.err.println( usage ); } return 1; } // Try to work out what hosts all the profiles are running on. boolean allLocal = true; String[] hosts = new String[ profiles.length ]; for ( int ip = 0; ip < profiles.length; ip++ ) { ClientProfile profile = profiles[ ip ]; String host = null; if ( profile == standardProfile ) { host = SampUtils.getLocalhost(); } else if ( profile instanceof StandardClientProfile ) { URL xurl = ((StandardClientProfile) profile).getLockInfo() .getXmlrpcUrl(); if ( xurl != null ) { host = xurl.getHost(); } } allLocal = allLocal && isLocalHost( host ); hosts[ ip ] = host; } // Work out whether to export URLs. final boolean exporturls; if ( reqExportUrls == null ) { if ( allLocal ) { logger_.info( "All hubs apparently on local host; " + "no URL exporting will be attempted" ); exporturls = false; } else { logger_.info( "Bridge apparently running between hosts; " + "URL exporting will be attempted" ); exporturls = true; } } else { exporturls = reqExportUrls.booleanValue(); logger_.info( "By request, URL exporting " + ( exporturls ? "will" : "will not" ) + " be attempted" ); } // Create a bridge. Bridge bridge = new Bridge( profiles ); // Arrange to export URLs if appropriate. if ( exporturls ) { for ( int ip = 0; ip < profiles.length; ip++ ) { String host = hosts[ ip ]; if ( host != null ) { try { InetAddress addr = InetAddress.getByName( host ); if ( addr.isLoopbackAddress() ) { addr = InetAddress .getByName( SampUtils.getLocalhost() ); } String ehost = addr.getCanonicalHostName(); bridge.exportUrls( ip, ehost ); } catch ( UnknownHostException e ) { logger_.log( Level.WARNING, "Can't export URLs for host " + host, e ); } } } } // Start the bridge. if ( ! bridge.start() ) { System.err.println( "Couldn't contact all hubs" ); return 1; } // Wait until there's only one hub connected. try { synchronized ( bridge ) { while ( bridge.getConnectionCount() > 1 ) { bridge.wait(); } } } catch ( InterruptedException e ) { } return 0; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/IconAdjuster.java000066400000000000000000000130771356450004300257020ustar00rootroot00000000000000package org.astrogrid.samp.bridge; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.httpd.HttpServer; /** * HttpServer handler for turning the URL of one icon into the URL of * another, related icon. * * @author Mark Taylor * @since 23 Jul 2009 */ abstract class IconAdjuster implements HttpServer.Handler { private final URL baseUrl_; private static final String OUTPUT_FORMAT_NAME = "png"; private static final String OUTPUT_MIME_TYPE = "image/png"; /** * Constructor. * * @param server server with which this handler will be used * @param basePath path at which the dynamic URLs generated by * this server will be rooted */ public IconAdjuster( HttpServer server, String basePath ) { if ( ! basePath.startsWith( "/" ) ) { basePath = "/" + basePath; } if ( ! basePath.endsWith( "/" ) ) { basePath = basePath + "/"; } try { baseUrl_ = new URL( server.getBaseUrl(), basePath ); } catch ( MalformedURLException e ) { throw (AssertionError) new AssertionError().initCause( e ); } } /** * Produces an adjusted image for serving. * * @param inImage input image * @return adjusted version of inImage */ public abstract RenderedImage adjustImage( BufferedImage inImage ); /** * Returns a URL at which the dynamically adjusted version of the icon * at the given URL will be served. * * @param iconUrl URL of existing icon (GIF, PNG or JPEG) */ public URL exportAdjustedIcon( URL iconUrl ) { try { return new URL( baseUrl_ + "?" + SampUtils.uriEncode( iconUrl.toString() ) ); } catch ( MalformedURLException e ) { throw (AssertionError) new AssertionError().initCause( e ); } } /** * Returns the URL at which the underlying icon for the one represented * by the given server path. The resourcePath should be * the path part of a URL returned from an earlier call to * {@link #exportAdjustedIcon}. * * @param resourcePath path part of a URL requesting an adjusted icon * @return original icon corresponding to resourcePath, or null * if it doesn't look like a path this object dispensed */ private URL getOriginalUrl( String resourcePath ) throws MalformedURLException { // If there's no query part, it's not one of ours. URL resourceUrl = new URL( baseUrl_, resourcePath ); String query = resourceUrl.getQuery(); if ( query == null ) { return null; } else { // If the base does not match our base URL, it's not one of ours. String base = resourceUrl.toString(); base = base.substring( 0, base.length() - query.length() ); if ( ! base.equals( baseUrl_.toString() + "?" ) ) { return null; } // Otherwise, try to interpret the query as a URL. // It should be the URL of the original icon. // If it's not, an exception will result. String qurl; try { qurl = SampUtils.uriDecode( query ); } catch ( RuntimeException e ) { throw (MalformedURLException) new MalformedURLException().initCause( e ); } return new URL( qurl ); } } public HttpServer.Response serveRequest( HttpServer.Request request ) { URL baseIconUrl; try { baseIconUrl = getOriginalUrl( request.getUrl() ); } catch ( MalformedURLException e ) { return HttpServer.createErrorResponse( 404, "Not found", e ); } if ( baseIconUrl == null ) { return null; } // Prepare the headers. Map hdrMap = new HashMap(); hdrMap.put( "Content-Type", OUTPUT_MIME_TYPE ); // Generate the response object. String method = request.getMethod(); if ( "HEAD".equals( method ) ) { return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) { } }; } else if ( "GET".equals( method ) ) { BufferedImage baseImage; try { baseImage = ImageIO.read( baseIconUrl ); if ( baseImage == null ) { throw new FileNotFoundException( baseIconUrl.toString() ); } } catch ( IOException e ) { return HttpServer .createErrorResponse( 500, "Server I/O error", e ); } final RenderedImage adjustedImage = adjustImage( baseImage ); return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) throws IOException { ImageIO.write( adjustedImage, OUTPUT_FORMAT_NAME, out ); } }; } else { return HttpServer .create405Response( new String[] { "GET", "HEAD" } ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/ProxyManager.java000066400000000000000000000765101356450004300257250ustar00rootroot00000000000000package org.astrogrid.samp.bridge; import java.awt.Image; import java.awt.AlphaComposite; import java.awt.Composite; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.astrogrid.samp.Client; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.HubConnector; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.client.TrackedClientSet; import org.astrogrid.samp.httpd.HttpServer; import org.astrogrid.samp.httpd.UtilServer; /** * Takes care of client connections for the SAMP Bridge. * An instance of this class is associated with a given 'local' hub * participating in the bridge, and makes the following connections: *

    *
  1. On the local hub, one connection which is there to monitor * client changes
  2. *
  3. On each remote hub participating in the bridge, one 'proxy' connection * for every client on this manager's local hub.
  4. *
* Callbacks from the hub to the proxy clients can be tunnelled by this * proxy manager to their true destination on the local hub. * Note that each proxy manager needs the cooperation of all the other * proxy managers (the ones associated with the other bridged hubs) to * make this work, so each instance of this class must be made aware of * the other ProxyMangers before use (see {@link #init}). * * @author Mark Taylor * @since 15 Jul 2009 */ class ProxyManager { private final ClientProfile localProfile_; private final UtilServer server_; private final HubConnector pmConnector_; private final Map connectionMap_; // local client ID -> HubConnection[] private final Map tagMap_; private final IconAdjuster iconAdjuster_; private ProxyManager[] remoteManagers_; private UrlExporter exporter_; private boolean useProxyHub_; private int nRemote_; private static final Logger logger_ = Logger.getLogger( ProxyManager.class.getName() ); /** * Constructor. * * @param localProfile profile for connection to this manager's local hub * @param server server instance */ public ProxyManager( ClientProfile localProfile, UtilServer server ) { localProfile_ = localProfile; server_ = server; // Set up the local hub connection to monitor client list changes. pmConnector_ = new HubConnector( localProfile, new ProxyManagerClientSet() ) { protected void connectionChanged( boolean isConnected ) { super.connectionChanged( isConnected ); managerConnectionChanged( isConnected ); } }; Metadata meta = new Metadata(); meta.setName( "bridge" ); meta.setDescriptionText( "Bridge between hubs" ); try { meta.setIconUrl( server_ .exportResource( "/org/astrogrid/samp/images/" + "bridge.png" ) .toString() ); } catch ( IOException e ) { logger_.warning( "Couldn't set icon" ); } meta.put( "author.name", "Mark Taylor" ); meta.put( "author.email", "m.b.taylor@bristol.ac.uk" ); pmConnector_.declareMetadata( meta ); Subscriptions subs = pmConnector_.computeSubscriptions(); pmConnector_.declareSubscriptions( subs ); // Set up other required data structures. connectionMap_ = Collections.synchronizedMap( new HashMap() ); tagMap_ = Collections.synchronizedMap( new HashMap() ); iconAdjuster_ = new ProxyIconAdjuster(); server_.getServer().addHandler( iconAdjuster_ ); } /** * Returns the profile for this manager's local hub. * * @return profile */ public ClientProfile getProfile() { return localProfile_; } /** * Returns the hub connector used by this manager for client monitoring * on the local hub. * * @return hub connector */ public HubConnector getManagerConnector() { return pmConnector_; } /** * Sets an object which is used to export SAMP data contents for use * in remote contexts. * * @param exporter new exporter; may be null */ public void setExporter( UrlExporter exporter ) { exporter_ = exporter; } /** * Sets whether remote proxy should be generated for the local client * representing the local hub. * Default is not, since they are not very interesting to talk to. * * @param useProxyHub true iff the client representing the local hub * should be proxied remotely */ public void setUseProxyHub( boolean useProxyHub ) { useProxyHub_ = useProxyHub; } /** * Prepares this manager for use by informing it about all its sibling * managers. This must be done before the bridge can start operations. * * @param allManagers array of ProxyManagers including this one, * one for each hub participating in the bridge */ public void init( ProxyManager[] allManagers ) { // Store an array of all the other managers, excluding this one, // for later use. List remoteList = new ArrayList(); int selfCount = 0; for ( int im = 0; im < allManagers.length; im++ ) { ProxyManager pm = allManagers[ im ]; if ( pm == this ) { selfCount++; } else { remoteList.add( pm ); } } if ( selfCount != 1 ) { throw new IllegalArgumentException( "Self not in list once" ); } remoteManagers_ = (ProxyManager[]) remoteList.toArray( new ProxyManager[ 0 ] ); nRemote_ = remoteManagers_.length; assert nRemote_ == allManagers.length - 1; } public String toString() { return localProfile_.toString(); } /** * Returns the connection on the hub associated with a remote * proxy manager which is the proxy for a given local client. * * @param remoteManager proxy manager for a remote bridged hub * @param localClientId client ID of a client registered with * this manager's local hub * @return proxy connection */ private HubConnection getProxyConnection( ProxyManager remoteManager, String localClientId ) { HubConnection[] proxyConnections = (HubConnection[]) connectionMap_.get( localClientId ); return proxyConnections == null ? null : proxyConnections[ getManagerIndex( remoteManager ) ]; } /** * Deletes the record of the connection on the hub associated with * a remote proxy manager which is the proxy for a given local client. * This proxy can no longer be used. * * @param remoteManager proxy manager for a remote bridged hub * @param localClientId client ID of a client registered with * this manager's local hub */ private void removeProxyConnection( ProxyManager remoteManager, String localClientId ) { HubConnection[] proxyConnections = (HubConnection[]) connectionMap_.get( localClientId ); if ( proxyConnections != null ) { proxyConnections[ getManagerIndex( remoteManager ) ] = null; } } /** * Returns the index by which this manager labels a given remote * proxy manager. * * @param remoteManager manager to locate * @return index of remoteManager in the list */ private int getManagerIndex( ProxyManager remoteManager ) { return Arrays.asList( remoteManagers_ ).indexOf( remoteManager ); } /** * Returns the metadata to use for the remote proxy of a local client. * This resembles the metadata of the local client itself, but may * have some adjustments. * * @param localClient local client * @return metadata to use for client's remote proxy */ private Metadata getProxyMetadata( Client localClient ) { Metadata meta = localClient.getMetadata(); if ( meta == null ) { return null; } else { meta = new Metadata( meta ); if ( exporter_ != null ) { exporter_.exportMap( meta ); } meta.setName( proxyName( meta.getName() ) ); if ( meta.getIconUrl() != null ) { URL iconUrl = proxyIconUrl( meta.getIconUrl() ); meta.setIconUrl( iconUrl == null ? null : iconUrl.toString() ); } meta.put( "bridge.proxy.source", ProxyManager.this.toString() ); } return meta; } /** * Returns the name to be used for a proxy client given its local name. * * @param localName local name * @return proxy name */ private String proxyName( String localName ) { return localName == null ? "(proxy)" : localName + " (proxy)"; } /** * Returns the icon to be used for a proxy client given its local icon. * * @param localIconUrl URL for local icon * @return URL for proxy icon */ private URL proxyIconUrl( URL localIconUrl ) { return localIconUrl != null ? iconAdjuster_.exportAdjustedIcon( localIconUrl ) : localIconUrl; } /** * Returns the subscriptions to use for the remote proxy of a local client. * This resembles the subscriptions of the local client itself, but may * have some adjustments. * * @param localClient local client * @return subscriptions to use for client's remote proxy */ private Subscriptions getProxySubscriptions( Client client ) { Subscriptions subs = client.getSubscriptions(); if ( subs == null ) { return null; } else { // Remove subscriptions to most hub administrative MTypes. // These should not be delivered from the remote hub to the // local client, since the local client should only receive // such messages from its own hub. Note this does not mean // that the local client will not be informed about changes // to clients on the remote hubs; this information will be // relayed by the local hub as a consequence of proxies from // other ProxyManagers making register/declare/etc calls // on this manager's local hub. subs = new Subscriptions( subs ); subs.remove( "samp.hub.event.shutdown" ); subs.remove( "samp.hub.event.register" ); subs.remove( "samp.hub.event.unregister" ); subs.remove( "samp.hub.event.metadata" ); subs.remove( "samp.hub.event.subscriptions" ); if ( exporter_ != null ) { exporter_.exportMap( subs ); } return subs; } } /** * Called when this ProxyManager's connector has been disconnected * (for whatever reason) from its local hub. * It makes sure that any proxies from other ProxyManagers to the local * hub are unregistered, so that no further bridge activity takes * place on the local hub. * * @param isConnected true for a connection; false for a disconnection */ protected void managerConnectionChanged( boolean isConnected ) { if ( ! isConnected ) { for ( int ir = 0; ir < nRemote_; ir++ ) { ProxyManager remoteManager = remoteManagers_[ ir ]; int im = remoteManager.getManagerIndex( this ); for ( Iterator it = remoteManager.connectionMap_.values() .iterator(); it.hasNext(); ) { HubConnection[] connections = (HubConnection[]) it.next(); if ( connections != null ) { HubConnection connection = connections[ im ]; if ( connection != null ) { connections[ im ] = null; try { connection.unregister(); } catch ( SampException e ) { logger_.info( "Unregister failed" ); } } } } } } else { // not expected except for initial connection } } /** * Invoked when a client is added to the local hub. * * @param client newly added client */ private void localClientAdded( Client client ) { if ( ! isProxiedClient( client ) ) { return; } // Register a proxy for the new local client on all the remote hubs // in the bridge. Metadata meta = getProxyMetadata( client ); Subscriptions subs = getProxySubscriptions( client ); HubConnection[] proxyConnections = new HubConnection[ nRemote_ ]; connectionMap_.put( client.getId(), proxyConnections ); for ( int ir = 0; ir < nRemote_; ir++ ) { ProxyManager remoteManager = remoteManagers_[ ir ]; try { // This synchronization is here so that the isProxy method // can work reliably. isProxy may ask whether a client seen // on a remote hub is a proxy controlled by this one. // It can only ask after the registration has been done, // and the determination is synchronized on connectionMap_. // By synchronizing here, we can ensure that it can't ask // after the registration, but before the information has // been recorded in the connectionMap. final HubConnection proxyConnection; synchronized ( connectionMap_ ) { proxyConnection = remoteManager.getProfile().register(); if ( proxyConnection != null ) { CallableClient callable = new ProxyCallableClient( client, proxyConnection, remoteManager ); proxyConnection.setCallable( callable ); proxyConnections[ ir ] = proxyConnection; } } if ( proxyConnection != null ) { if ( meta != null ) { try { proxyConnection.declareMetadata( meta ); } catch ( SampException e ) { logger_.warning( "proxy declareMetadata failed for " + client ); } } if ( subs != null ) { try { proxyConnection.declareSubscriptions( subs ); } catch ( SampException e ) { logger_.warning( "proxy declareSubscriptions failed" + " for " + client ); } } } } catch ( SampException e ) { logger_.warning( "proxy registration failed for " + client ); } } } /** * Invoked when a client is removed from the local hub. * * @param client recently removed client */ private void localClientRemoved( Client client ) { if ( ! isProxiedClient( client ) ) { return; } // Remove all the proxies which were registered on remote hubs // on behalf of the removed client. HubConnection[] proxyConnections = (HubConnection[]) connectionMap_.remove( client.getId() ); if ( proxyConnections != null ) { for ( int ir = 0; ir < nRemote_; ir++ ) { HubConnection connection = proxyConnections[ ir ]; if ( connection != null ) { try { connection.unregister(); } catch ( SampException e ) { logger_.warning( "proxy unregister failed for " + client ); } } } } } /** * Invoked when information (metadata or subscriptions) have been * updated for a client on the local hub. * * @param client updated client * @param metaChanged true if metadata may have changed * (false if known unchanged) * @param subsChanged true if subscriptions may have changed * (false if known unchanged) */ private void localClientUpdated( Client client, boolean metaChanged, boolean subsChanged ) { if ( ! isProxiedClient( client ) ) { return; } // Cause each of the local client's proxies on remote hubs to // declare subscription/metadata updates appropriately. HubConnection[] proxyConnections = (HubConnection[]) connectionMap_.get( client.getId() ); Metadata meta = metaChanged ? getProxyMetadata( client ) : null; Subscriptions subs = subsChanged ? getProxySubscriptions( client ) : null; if ( proxyConnections != null ) { for ( int ir = 0; ir < nRemote_; ir++ ) { HubConnection connection = proxyConnections[ ir ]; if ( connection != null ) { if ( meta != null ) { try { connection.declareMetadata( meta ); } catch ( SampException e ) { logger_.warning( "proxy declareMetadata failed " + "for " + client ); } } if ( subs != null ) { try { connection.declareSubscriptions( subs ); } catch ( SampException e ) { logger_.warning( "proxy declareSubscriptions " + "failed for " + client ); } } } } } } /** * Determines whether a local client is a genuine third party client * which requires a remote proxy. Will return false for clients which * are operating on behalf of this bridge, including the ProxyManager's * client tracking connection and any proxies controlled by remote * ProxyManagers. Unless useProxyHub is true, will also return false * for the hub client on remote hubs, since these are not very * interesting to talk to. * * @param client local client * @param true if client has or should have a proxy; * false if it's an organ of the bridge administration */ private boolean isProxiedClient( Client client ) { // Is it a client on the local hub that we want to exclude? try { if ( pmConnector_.isConnected() ) { HubConnection connection = pmConnector_.getConnection(); if ( connection != null ) { String clientId = client.getId(); RegInfo regInfo = connection.getRegInfo(); if ( clientId.equals( regInfo.getSelfId() ) || ( ! useProxyHub_ && clientId.equals( regInfo.getHubId() ) ) ) { return false; } } } } catch ( SampException e ) { } // Is it a proxy controlled by one of the remote managers? for ( int ir = 0; ir < nRemote_; ir++ ) { if ( remoteManagers_[ ir ].isProxy( client, ProxyManager.this ) ) { return false; } } // No, then it's a genuine local client requiring a proxy. return true; } /** * Determines whether a given local client is a proxy controlled by * a given remote ProxyManager. * * @param client local client * @param remoteManager remote proxy manager * @return true iff client is one of * remoteManager's proxies */ private boolean isProxy( Client client, ProxyManager remoteManager ) { int ir = getManagerIndex( remoteManager ); synchronized ( connectionMap_ ) { for ( Iterator it = connectionMap_.values().iterator(); it.hasNext(); ) { HubConnection[] proxyConnections = (HubConnection[]) it.next(); if ( proxyConnections != null ) { HubConnection proxyConnection = proxyConnections[ ir ]; if ( proxyConnection != null ) { RegInfo proxyReg = proxyConnection.getRegInfo(); if ( proxyReg.getSelfId().equals( client.getId() ) ) { return true; } } } } } return false; } /** * CallableClient implementation used by remote proxy connections on * behalf of local clients. This is the core of the proxy manager. * Callbacks received by the remote proxy client are tunnelled back * to the local hub and forwarded by the local proxy of the remote * sender client to the appropriate local non-proxy client. * Since local proxies are managed by other proxy managers * (this one manages remote proxies of local clients) * this means getting the other proxy managers to do some of the work. */ private class ProxyCallableClient implements CallableClient { private final String localClientId_; private final HubConnection remoteProxy_; private final ProxyManager remoteManager_; private final ProxyManager localManager_; /** * Constructor. * * @param localClient local client * @param remoteProxy hub connection to the remote hub for the proxy * @param remoteManager remote ProxyManager associated with the * hub where this proxy is connected */ ProxyCallableClient( Client localClient, HubConnection remoteProxy, ProxyManager remoteManager ) { localClientId_ = localClient.getId(); remoteProxy_ = remoteProxy; remoteManager_ = remoteManager; localManager_ = ProxyManager.this; } public void receiveNotification( String remoteSenderId, Message msg ) throws SampException { // Forward the notification. if ( remoteManager_.exporter_ != null ) { msg = new Message( msg ); remoteManager_.exporter_.exportMap( msg ); } HubConnection localProxy = getLocalProxy( remoteSenderId ); if ( localProxy != null ) { localProxy.notify( localClientId_, msg ); } proxyProcessMessage( remoteSenderId, msg ); } public void receiveCall( String remoteSenderId, String remoteMsgId, Message msg ) throws SampException { // Choose a tag; use the message ID as its value. // These things are different, but we are free to choose any // form for the tag, and we need something which will allow // us to recover the message ID from it later. // Making them identical is the easiest way to do that. String localMsgTag = remoteMsgId; // Forward the call. if ( remoteManager_.exporter_ != null ) { msg = new Message( msg ); remoteManager_.exporter_.exportMap( msg ); } HubConnection localProxy = getLocalProxy( remoteSenderId ); if ( localProxy != null ) { localProxy.call( localClientId_, localMsgTag, msg ); } else { ErrInfo errInfo = new ErrInfo(); errInfo.setErrortxt( "Bridge can't forward message" ); Client senderClient = (Client) remoteManager_.getManagerConnector() .getClientMap().get( remoteSenderId ); String usertxt = new StringBuffer() .append( "Bridge can't forward message to recipient;\n" ) .append( "sender client " ) .append( senderClient ) .append( " has no proxy on remote hub" ) .toString(); errInfo.setUsertxt( usertxt ); new ErrInfo( "Client " + remoteSenderId + " not present" + " on other side of bridge" ); remoteProxy_.reply( remoteMsgId, Response.createErrorResponse( errInfo ) ); } proxyProcessMessage( remoteSenderId, msg ); } public void receiveResponse( String remoteResponderId, String remoteMsgTag, Response response ) throws SampException { // The message ID we need for forwarding is the one we encoded // (by identity) earlier in the tag. String localMsgId = remoteMsgTag; // Forward the reply appropriately. if ( remoteManager_.exporter_ != null ) { response = new Response( response ); remoteManager_.exporter_.exportMap( response ); } HubConnection localProxy = getLocalProxy( remoteResponderId ); if ( localProxy != null ) { localProxy.reply( localMsgId, response ); } else { // Should only happen if the proxy has been disconnected // between send and receive. logger_.warning( "Bridge can't forward response: " + " missing proxy" ); } } /** * Returns the hub connection for the proxy on the local hub * which corresponds to a given remote client. * * @param remoteClientId client ID of remote client * @return hub connection for local proxy */ private HubConnection getLocalProxy( String remoteClientId ) { return remoteManager_ .getProxyConnection( localManager_, remoteClientId ); } /** * Performs housekeeping tasks for an incoming message if any. * This is in addition to forwarding the message to the client * for which we are proxying. * * @param remoteSenderId id of sending client on remote hub * @param msg message */ private void proxyProcessMessage( String remoteSenderId, Message msg ) { String mtype = msg.getMType(); boolean fromHub = remoteSenderId.equals( remoteProxy_.getRegInfo().getHubId() ); if ( "samp.hub.disconnect".equals( mtype ) ) { if ( fromHub ) { removeProxyConnection( remoteManager_, localClientId_ ); } else { logger_.warning( mtype + " from non-hub client " + remoteSenderId + " - ignored" ); } } } } /** * TrackedClientSet implementation used by a Proxy Manager. * Apart from inheriting default behaviour, this triggers * calls to ProxyManager methods when there are status changes * to local clients. */ private class ProxyManagerClientSet extends TrackedClientSet { /** * Constructor. */ private ProxyManagerClientSet() { super(); } public void addClient( Client client ) { super.addClient( client ); localClientAdded( client ); } public void removeClient( Client client ) { localClientRemoved( client ); super.removeClient( client ); } public void updateClient( Client client, boolean metaChanged, boolean subsChanged ) { super.updateClient( client, metaChanged, subsChanged ); localClientUpdated( client, metaChanged, subsChanged ); } public void setClients( Client[] clients ) { for ( Iterator it = getClientMap().entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); Client client = (Client) entry.getValue(); localClientRemoved( client ); } super.setClients( clients ); for ( int i = 0; i < clients.length; i++ ) { Client client = clients[ i ]; localClientAdded( client ); } } } /** * Class which can turn a client's icon into the icon for the proxy of * the same client. Some visually distinctive adjustment is made to * make it obvious from the icon that it's a proxy. */ private class ProxyIconAdjuster extends IconAdjuster { /** * Constructor. */ ProxyIconAdjuster() { super( server_.getServer(), server_.getBasePath( "proxy" + "-" + localProfile_ ) ); } public RenderedImage adjustImage( BufferedImage inImage ) { int w = inImage.getWidth(); int h = inImage.getHeight(); // Copy the image to a new image. It would be possible to write // directly into the input BufferedImage, but this might not // have the correct image type, so could end up getting the // transparency wrong or something. BufferedImage outImage = new BufferedImage( w, h, BufferedImage.TYPE_4BYTE_ABGR ); Graphics2D g2 = outImage.createGraphics(); g2.drawImage( inImage, null, 0, 0 ); // Slice off half of the image diagonally. int[] xs = new int[] { 0, w, w, }; int[] ys = new int[] { h, h, 0, }; Composite compos = g2.getComposite(); g2.setComposite( AlphaComposite.Clear ); g2.fillPolygon( xs, ys, 3 ); g2.setComposite( compos ); // Return the result. return outImage; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/UrlExporter.java000066400000000000000000000124301356450004300255730ustar00rootroot00000000000000package org.astrogrid.samp.bridge; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.astrogrid.samp.httpd.UtilServer; /** * Exports SAMP data objects which have been created on a given host * for use in a remote context. The job that needs doing is to * convert URLs which reference the host in a way that only makes sense * locally (as a loopback address 127.0.0.1 or localhost) to a form * which can be used on remote hosts. * *

This is not an exact science; a best effort is made. * * @author Mark Taylor * @since 29 Jul 2009 */ class UrlExporter { private final String host_; private final boolean exportFiles_; private static final Logger logger_ = Logger.getLogger( UrlExporter.class.getName() ); private static final Pattern LOCALHOST_REGEX = Pattern.compile( "(http://|ftp://)" + "(127\\.0\\.0\\.1|localhost)" + "([:/].*)" ); private static final Pattern FILE_REGEX = Pattern.compile( "(file://)" + "([^/]*)" + "/.*" ); /** * Constructor. * * @param host public name of the host to which loopback addresses * refer * @param exportFiles whether to export file-protocol URLs * by turning them into http ones; * this only makes sense if the current JVM * is running on a machine which can see * host's filesystem(s) */ public UrlExporter( String host, boolean exportFiles ) { host_ = host; exportFiles_ = exportFiles; } /** * Exports a single string for remote usage. * If it looks like a URL, it's changed. Not foolproof. * * @param text string to assess * @return copy of text if it's not a URL, otherwise a possibly * edited URL with the same content */ public String exportString( String text ) { String t2 = doExportString( text ); if ( t2 != null && ! t2.equals( text ) ) { logger_.info( "Exported string \"" + text + "\" -> \"" + t2 + '"' ); } return t2; } /** * Does the work for {@link #exportString}. * * @param text string to assess * @return copy of text if it's not a URL, otherwise a URL with a * possibly edited host part */ private String doExportString( String text ) { Matcher localMatcher = LOCALHOST_REGEX.matcher( text ); if ( localMatcher.matches() ) { return localMatcher.group( 1 ) + host_ + localMatcher.group( 3 ); } else if ( exportFiles_ && FILE_REGEX.matcher( text ).matches() ) { try { URL fileUrl = new URL( text ); String path = fileUrl.getPath(); if ( File.separatorChar != '/' ) { path = path.replace( '/', File.separatorChar ); } File file = new File( path ); if ( file.canRead() && ! file.isDirectory() ) { URL expUrl = UtilServer.getInstance() .getMapperHandler() .addLocalUrl( fileUrl ); if ( expUrl != null ) { return expUrl.toString(); } } } catch ( MalformedURLException e ) { // not a URL at all - don't attempt to export it } catch ( IOException e ) { // something else went wrong - leave alone } return text; } else { return text; } } /** * Exports a list for remote usage by changing its contents in place. * * @param list list to edit */ public void exportList( List list ) { for ( ListIterator it = list.listIterator(); it.hasNext(); ) { Object value = it.next(); if ( value instanceof String ) { it.set( exportString( (String) value ) ); } else if ( value instanceof List ) { exportList( (List) value ); } else if ( value instanceof Map ) { exportMap( (Map) value ); } } } /** * Exports a map for remote usage by changing its contents in place. * * @param map map to edit */ public void exportMap( Map map ) { for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); Object value = entry.getValue(); if ( value instanceof String ) { entry.setValue( exportString( (String) value ) ); } else if ( value instanceof List ) { exportList( (List) value ); } else if ( value instanceof Map ) { exportMap( (Map) value ); } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/bridge/package.html000066400000000000000000000003061356450004300247150ustar00rootroot00000000000000 Classes for the SAMP Bridge. The {@link org.astrogrid.samp.bridge.Bridge} class is an application which can connect two or more hubs, allowing the clients on each to see the others. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/000077500000000000000000000000001356450004300224575ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/AbstractMessageHandler.java000066400000000000000000000123421356450004300276720ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampMap; import org.astrogrid.samp.Subscriptions; /** * Partial implementation of MessageHandler interface which helps to ensure * correct client behaviour. * Concrete subclasses just need to specify the MTypes they subscribe to * and implement the {@link #processCall} method. * * @author Mark Taylor * @since 16 Jul 2008 */ public abstract class AbstractMessageHandler implements MessageHandler { private Subscriptions subscriptions_; private final Logger logger_ = Logger.getLogger( AbstractMessageHandler.class.getName() ); /** * Constructor using a given subscriptions map. * * @param subscriptions {@link org.astrogrid.samp.Subscriptions}-like map * defining which MTypes this handler can process */ protected AbstractMessageHandler( Map subscriptions ) { setSubscriptions( subscriptions ); } /** * Constructor using a given list of subscribed MTypes. * * @param mtypes list of MTypes which this handler can process */ protected AbstractMessageHandler( String[] mtypes ) { Map subs = new HashMap(); for ( int i = 0; i < mtypes.length; i++ ) { subs.put( mtypes[ i ], new HashMap() ); } setSubscriptions( subs ); } /** * Constructor using a single subscribed MType. * * @param mtype single MType which this handler can process */ protected AbstractMessageHandler( String mtype ) { this( new String[] { mtype } ); } /** * Implements message processing. Implementations should normally * return a map which contains the samp.result part * of the call response, that is the MType-specific return value * name->value map. * As a special case, returning null is equivalent to returning an empty * map. * However, if {@link #createResponse} is overridden, the return value * semantics may be different. * * @param connection hub connection * @param senderId public ID of sender client * @param message message with MType this handler is subscribed to * @return result of handling this message; exact semantics determined * by {@link #createResponse createResponse} implementation */ public abstract Map processCall( HubConnection connection, String senderId, Message message ) throws Exception; /** * Invoked by {@link #receiveCall receiveCall} to create a success * response from the result of calling {@link #processCall processCall}. * *

The default implementation calls * {@link Response#createSuccessResponse}(processOutput), * first transforming a null value to an empty map for convenience. * However, it may be overridden for more flexibility (for instance * in order to return non-OK responses). * * @param processOutput a Map returned by {@link #processCall processCall} */ protected Response createResponse( Map processOutput ) { Map result = processOutput == null ? SampMap.EMPTY : processOutput; return Response.createSuccessResponse( result ); } /** * Sets the subscriptions map. Usually this is called by the constructor, * but it may be reset manually. * * @param subscriptions {@link org.astrogrid.samp.Subscriptions}-like map * defining which MTypes this handler can process */ public void setSubscriptions( Map subscriptions ) { Subscriptions subs = Subscriptions.asSubscriptions( subscriptions ); subs.check(); subscriptions_ = subs; } public Map getSubscriptions() { return subscriptions_; } /** * Calls {@link #processCall} and discards the result. */ public void receiveNotification( HubConnection connection, String senderId, Message message ) { try { processCall( connection, senderId, message ); } catch ( Throwable e ) { logger_.log( Level.INFO, "Error processing notification " + message.getMType() + " - ignored", e ); } } /** * Calls {@link #processCall}, generates a response from the result * using {@link #createResponse}, and sends the resulting response * as a reply to the hub. In case of an exception, a suitable error * response is sent instead. */ public void receiveCall( HubConnection connection, String senderId, String msgId, Message message ) throws SampException { Response response; try { response = createResponse( processCall( connection, senderId, message ) ); } catch ( Throwable e ) { response = Response.createErrorResponse( new ErrInfo( e ) ); } connection.reply( msgId, response ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/CallableClient.java000066400000000000000000000026171356450004300261660ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.util.Map; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; /** * Defines callbacks which the hub can make on a callable client. * * @author Mark Taylor * @since 16 Jul 2008 */ public interface CallableClient { /** * Receives a message for which no response is required. * * @param senderId public ID of sending client * @param message message */ void receiveNotification( String senderId, Message message ) throws Exception; /** * Receives a message for which a response is required. * The implementation must take care to call the hub's reply * method at some future point. * * @param senderId public ID of sending client * @param msgId message identifier for later use with reply * @param message message */ void receiveCall( String senderId, String msgId, Message message ) throws Exception; /** * Receives a response to a message previously sent by this client. * * @param responderId public ID of responding client * @param msgTag client-defined tag labelling previously-sent message * @param response returned response object */ void receiveResponse( String responderId, String msgTag, Response response ) throws Exception; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/ClientProfile.java000066400000000000000000000030051356450004300260570ustar00rootroot00000000000000package org.astrogrid.samp.client; /** * Defines an object that can be used to register with a running SAMP hub. * Registration takes the form of providing a connection object which * a client can use to perform further hub interactions. * Client-side implementations will take care of communications, * mapping between profile-specific transport mechanisms and the * methods of the generated {@link HubConnection} objects. * *

The usual way for a prospective SAMP client to obtain an instance of * this class is by using {@link DefaultClientProfile#getProfile}. * *

This interface is so-named partly for historical reasons; * "HubConnectionFactory" might have been more appropriate. * * @author Mark Taylor * @since 15 Jul 2008 */ public interface ClientProfile { /** * Attempts to register with a SAMP hub and return a corresponding * connection object. Some profile-specific hub discovery mechanism * is used to locate the hub. * If no hub is running, null will normally be returned. * * @return hub connection representing a new registration, or null * @throws SampException in case of some unexpected error */ HubConnection register() throws SampException; /** * Indicates whether a hub contactable by this profile appears to be * running. This is intended to execute reasonably quickly. * It should not go as far as registering. * * @return true iff it looks like a hub is running */ boolean isHubRunning(); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/ClientTracker.java000066400000000000000000000403531356450004300260610ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.logging.Logger; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.Subscriptions; /** * Message handler which watches hub event messages to keep track of * what clients are currently registered and what their attributes are * on behalf of the hub. * The results are stored in an externally supplied {@link TrackedClientSet} * object. This class tries its best to handle complications arising * from the fact that calls concerning a client may arrive out of order * (for instance declareMetadata before registration or after unregistration). * * @author Mark Taylor * @author Laurent Bourges * @since 16 Jul 2008 */ class ClientTracker extends AbstractMessageHandler { private final TrackedClientSet clientSet_; private final Map clientMap_; private final OperationQueue opQueue_; private final static Logger logger_ = Logger.getLogger( ClientTracker.class.getName() ); private static final int QUEUE_TIME = 10000; private static final String REGISTER_MTYPE; private static final String UNREGISTER_MTYPE; private static final String METADATA_MTYPE; private static final String SUBSCRIPTIONS_MTYPE; private static final String[] TRACKED_MTYPES = new String[] { REGISTER_MTYPE = "samp.hub.event.register", UNREGISTER_MTYPE = "samp.hub.event.unregister", METADATA_MTYPE = "samp.hub.event.metadata", SUBSCRIPTIONS_MTYPE = "samp.hub.event.subscriptions", }; /** * Constructor. * * @param clientSet object used to record registered clients and their * attributes */ public ClientTracker( TrackedClientSet clientSet ) { super( TRACKED_MTYPES ); clientSet_ = clientSet; clientMap_ = clientSet.getClientMap(); opQueue_ = new OperationQueue(); } /** * Removes all clients from the list. */ public void clear() { try { initialise( null ); } catch ( SampException e ) { assert false; } } /** * Initialises this tracker from a hub connection. * It is interrogated to find the current list of registered clients * and their attributes. * * @param connection hub connection; may be null for no connection */ public void initialise( HubConnection connection ) throws SampException { String[] clientIds; // If connection is null, there are no registered clients. if ( connection == null ) { clientIds = new String[ 0 ]; } // If connection is live, get the list of other registered clients, // and don't forget to add an entry for self, which // getRegisteredClients() excludes. else { String[] otherIds = connection.getRegisteredClients(); clientIds = new String[ otherIds.length + 1 ]; System.arraycopy( otherIds, 0, clientIds, 0, otherIds.length ); clientIds[ otherIds.length ] = connection.getRegInfo().getSelfId(); } // Prepare an array of client objects, populating their characteristics // by interrogating the connection. int nc = clientIds.length; TrackedClient[] clients = new TrackedClient[ nc ]; for ( int ic = 0; ic < nc; ic++ ) { String id = clientIds[ ic ]; TrackedClient client = new TrackedClient( id ); client.setMetadata( connection.getMetadata( id ) ); client.setSubscriptions( connection.getSubscriptions( id ) ); clients[ ic ] = client; } // Populate the client set. Discard any queued operations first. // This doesn't guarantee that we've got the most up to date // information ... but in absence of guaranteed delivery order for // messages that's more or less impossible. synchronized ( opQueue_ ) { ClientOperation[] pendingOps = opQueue_.getOperations(); opQueue_.clear(); clientSet_.setClients( clients ); } } public Map processCall( HubConnection connection, String senderId, Message message ) { String mtype = message.getMType(); if ( ! senderId.equals( connection.getRegInfo().getHubId() ) ) { logger_.warning( "Hub admin message " + mtype + " received from " + "non-hub client. Acting on it anyhow" ); } String id = (String) message.getParams().get( "id" ); if ( id == null ) { throw new IllegalArgumentException( "id parameter missing in " + mtype ); } String selfId = connection.getRegInfo().getSelfId(); if ( REGISTER_MTYPE.equals( mtype ) ) { TrackedClient client = new TrackedClient( id ); opQueue_.apply( client ); clientSet_.addClient( client ); } else if ( UNREGISTER_MTYPE.equals( mtype ) ) { performClientOperation( new ClientOperation( id, mtype ) { public void perform( TrackedClient client ) { opQueue_.discard( client ); clientSet_.removeClient( client ); } }, connection ); } else if ( METADATA_MTYPE.equals( mtype ) ) { final Map meta = (Map) message.getParams().get( "metadata" ); performClientOperation( new ClientOperation( id, mtype ) { public void perform( TrackedClient client ) { client.setMetadata( meta ); clientSet_.updateClient( client, true, false ); } }, connection ); } else if ( SUBSCRIPTIONS_MTYPE.equals( mtype ) ) { final Map subs = (Map) message.getParams().get( "subscriptions" ); performClientOperation( new ClientOperation( id, mtype ) { public void perform( TrackedClient client ) { client.setSubscriptions( subs ); clientSet_.updateClient( client, false, true ); } }, connection ); } else { throw new IllegalArgumentException( "Shouldn't have received MType" + mtype ); } return null; } /** * Performs an operation on a ClientOperation object. * * @param op client operation * @param connection hub connection */ private void performClientOperation( ClientOperation op, HubConnection connection ) { String id = op.getId(); // If the client is currently part of this tracker's data model, // we can peform the operation directly. TrackedClient client = (TrackedClient) clientMap_.get( id ); if ( client != null ) { op.perform( client ); } // If it's not, but it applies to this client itself, it's just // because we haven't added ourself to the client list yet. // Queue it. else if ( id.equals( connection.getRegInfo().getSelfId() ) ) { opQueue_.add( op ); } // Otherwise, the client is not yet known. This is most likely // because, in absence of any guarantee about message delivery order // within SAMP, a message which was sent between its registration // and its unregistration might still arrive either before its // registration event has arrived or after its unregistration event // has arrived. In the hope that it is the former, we hang on to // this operation so that it can be peformed at some future date // when we actually have a client object we can apply it to. else { // If it's for this client, this is just because it hasn't added // itself to the client list yet. Should get resolved very soon. if ( id.equals( connection.getRegInfo().getSelfId() ) ) { logger_.info( "Message " + op.getMType() + " arrived for self" + " - holding till later" ); } // Otherwise less certain, but we still hope. else { logger_.info( "No known client " + id + " for message " + op.getMType() + " - holding till later" ); } // Either way, queue it. opQueue_.add( op ); } } /** * Client implementation used to populate internal data structures. * It just implements the Client interface as well as adding mutators * for metadata and subscriptions, and providing an equals method based * on public id. */ private static class TrackedClient implements Client { private final String id_; private Metadata metadata_; private Subscriptions subscriptions_; /** * Constructor. * * @param id client public id */ public TrackedClient( String id ) { id_ = id; } public String getId() { return id_; } public Metadata getMetadata() { return metadata_; } public Subscriptions getSubscriptions() { return subscriptions_; } /** * Sets this client's metadata. * * @param metadata new metadata */ void setMetadata( Map metadata ) { metadata_ = Metadata.asMetadata( metadata ); } /** * Sets this client's subscriptions. * * @param subscriptions new subscriptions */ void setSubscriptions( Map subscriptions ) { subscriptions_ = Subscriptions.asSubscriptions( subscriptions ); } public boolean equals( Object o ) { if ( o instanceof TrackedClient ) { TrackedClient other = (TrackedClient) o; return other.id_.equals( this.id_ ); } else { return false; } } public int hashCode() { return id_.hashCode(); } public String toString() { return SampUtils.toString( this ); } } /** * Describes an operation to be performed on a TrackedClient object * which is already part of this tracker's model. */ private static abstract class ClientOperation { private final String id_; private final String mtype_; private final long birthday_; /** * Constructor. * * @param id client public ID * @param mtype MType of the message which triggered this operation */ ClientOperation( String id, String mtype ) { id_ = id; mtype_ = mtype; birthday_ = System.currentTimeMillis(); } /** * Performs the instance-specific operation on a given client. * * @param client client */ public abstract void perform( TrackedClient client ); /** * Returns the client ID for the client this operation applies to. * * @return client public ID */ public String getId() { return id_; } /** * Returns the MType of the message which triggered this operation. * * @return message MType */ public String getMType() { return mtype_; } /** * Returns the creation time of this object. * * @return System.currentTimeMillis() at construction */ public long getBirthday() { return birthday_; } public String toString() { return "message " + mtype_ + " for client " + id_; } } /** * Data structure for holding ClientOperation objects which (may) need * to be applied in the future. * Operations are dumped here if they cannot be performed immediately * because the client in question is not (yet) known by this tracker. * The hope is that the client will register at some point in the future * and the pending operations can be applied then. However, this may * never happen, so the queue maintains its own expiry system to throw * out old events. */ private static class OperationQueue { private final Collection opList_; private Timer tidyTimer_; /** * Constructor. */ OperationQueue() { opList_ = new ArrayList(); } /** * Add a new client operation which may get the opportunity to be * performed some time in the future. * * @param op oeration to add */ public synchronized void add( ClientOperation op ) { if ( tidyTimer_ == null ) { TimerTask tidy = new TimerTask() { public void run() { discardOld( QUEUE_TIME ); } }; tidyTimer_ = new Timer( true ); tidyTimer_.schedule( tidy, QUEUE_TIME, QUEUE_TIME ); } opList_.add( op ); } /** * Apply any pending operations to given client. * This client was presumably unavailable at the time such operations * were queued. * * @param client client to apply pending operations to */ public synchronized void apply( TrackedClient client ) { String id = client.getId(); for ( Iterator it = opList_.iterator(); it.hasNext(); ) { ClientOperation op = (ClientOperation) it.next(); if ( op.getId().equals( id ) ) { logger_.info( "Performing queued " + op ); op.perform( client ); it.remove(); } } } /** * Discards any operations corresponding to a given client, * presumably because the client is about to disappear. * * @param client client to forget about */ public synchronized void discard( TrackedClient client ) { String id = client.getId(); for ( Iterator it = opList_.iterator(); it.hasNext(); ) { ClientOperation op = (ClientOperation) it.next(); if ( op.getId().equals( id ) ) { logger_.warning( "Discarding queued " + op ); it.remove(); } } } /** * Throws away any pending operations which are older than a certain * age, presumably in the expectation that their client will never * register. * * @param maxAge oldest operations (in milliseconds) permitted to * remain in the queue */ public synchronized void discardOld( long maxAge ) { long now = System.currentTimeMillis(); for ( Iterator it = opList_.iterator(); it.hasNext(); ) { ClientOperation op = (ClientOperation) it.next(); if ( now - op.getBirthday() > maxAge ) { logger_.warning( "Discarding queued " + op + " - client never showed up" ); it.remove(); } } } /** * Removes all entries from this queue. */ public synchronized void clear() { opList_.clear(); if ( tidyTimer_ != null ) { tidyTimer_.cancel(); tidyTimer_ = null; } } /** * Returns an array containing all the operations currently pending. * * @return operation list */ public synchronized ClientOperation[] getOperations() { return (ClientOperation[]) opList_.toArray( new ClientOperation[ 0 ] ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/DefaultClientProfile.java000066400000000000000000000115051356450004300273700ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.util.logging.Logger; import org.astrogrid.samp.Platform; import org.astrogrid.samp.web.WebClientProfile; import org.astrogrid.samp.xmlrpc.StandardClientProfile; /** * Factory which supplies the default ClientProfile for use by SAMP clients. * By using this class to obtain ClientProfile instances, applications * can be used with non-standard profiles supplied at runtime without * requiring any code changes. * *

The profile returned by this class depends on the SAMP_HUB environment * variable ({@link #HUBLOC_ENV}). * If it consists of the prefix "jsamp-class:" * ({@link #HUBLOC_CLASS_PREFIX}) followed by the classname of a class * which implements {@link ClientProfile} and has a no-arg constructor, * then an instance of the named class is used. * Otherwise, an instance of {@link StandardClientProfile} or * {@link WebClientProfile} is returned. * * @author Mark Taylor * @since 4 Aug 2009 */ public class DefaultClientProfile { private static ClientProfile profile_; private static final Logger logger_ = Logger.getLogger( DefaultClientProfile.class.getName() ); /** Environment variable used for hub location ({@value}). */ public static final String HUBLOC_ENV = "SAMP_HUB"; /** * Prefix for SAMP_HUB env var indicating a supplied ClientProfile * implementation ({@value}). */ public static final String HUBLOC_CLASS_PREFIX = "jsamp-class:"; /** * No-arg constructor prevents instantiation. */ private DefaultClientProfile() { } /** * Returns a ClientProfile instance suitable for general purpose use. * By default this is currently the Standard Profile * ({@link org.astrogrid.samp.xmlrpc.StandardClientProfile#getInstance * StandardClientProfile.getInstance()}), * but the instance may be modified programatically or by use of * the SAMP_HUB environment variable. * *

If no instance has been set, the SAMP_HUB environment variable * is examined. If it consists of the prefix "jsamp-class:" * ({@link #HUBLOC_CLASS_PREFIX}) followed by the classname of a class * which implements {@link ClientProfile} and has a no-arg constructor, * then an instance of the named class is used. * Otherwise, an instance of {@link StandardClientProfile} is returned. * *

The instance is obtained lazily. * * @return client profile instance */ public static ClientProfile getProfile() { if ( profile_ == null ) { final ClientProfile profile; String hubloc = Platform.getPlatform().getEnv( HUBLOC_ENV ); if ( hubloc == null || hubloc.trim().length() == 0 ) { profile = StandardClientProfile.getInstance(); } else if ( hubloc.startsWith( HUBLOC_CLASS_PREFIX ) ) { String cname = hubloc.substring( HUBLOC_CLASS_PREFIX.length() ); final Class clazz; try { clazz = Class.forName( cname ); } catch ( ClassNotFoundException e ) { throw (IllegalArgumentException) new IllegalArgumentException( "No profile class " + cname ) .initCause( e ); } try { profile = (ClientProfile) clazz.newInstance(); logger_.info( "Using non-standard hub location: " + HUBLOC_ENV + "=" + hubloc ); } catch ( Throwable e ) { throw (RuntimeException) new RuntimeException( "Error instantiating custom " + "profile " + clazz.getName() ) .initCause( e ); } } else if ( hubloc.startsWith( StandardClientProfile .STDPROFILE_HUB_PREFIX ) ) { profile = StandardClientProfile.getInstance(); } else if ( hubloc.startsWith( WebClientProfile .WEBPROFILE_HUB_PREFIX ) ) { profile = WebClientProfile.getInstance(); } else { throw new RuntimeException( "Can't make sense of " + HUBLOC_ENV + "=" + hubloc ); } profile_ = profile; } return profile_; } /** * Sets the profile object which will be returned by {@link #getProfile}. * * @param profile default profile instance */ public static void setProfile( ClientProfile profile ) { profile_ = profile; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/HubConnection.java000066400000000000000000000134131356450004300260620ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.util.List; import java.util.Map; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; /** * Represents a registered client's connection to a running hub. * An application typically obtains an instance of this class * from a {@link ClientProfile} object. * *

It is good practice to call {@link #unregister} when the connection * is finished with; however if it is not called explicitly, the * connection will unregister itself on object finalisation or JVM termination, * as long as the JVM shuts down cleanly. * * @author Mark Taylor * @since 15 Jul 2008 */ public interface HubConnection { /** * Returns the registration information associated with this connection. * * @return registration info */ RegInfo getRegInfo(); /** * Tells the hub how it can perform callbacks on the client by providing * a CallableClient object. This is required before the client * can declare subscriptions or make asynchronous calls. * * @param callable callable client */ void setCallable( CallableClient callable ) throws SampException; /** * Tests whether the connection is currently open. * * @throws SampException if the hub has disappeared or communications * are disrupted in some other way */ void ping() throws SampException; /** * Unregisters the client and terminates this connection. */ void unregister() throws SampException; /** * Declares this registered client's metadata. * * @param meta {@link org.astrogrid.samp.Metadata}-like map */ void declareMetadata( Map meta ) throws SampException; /** * Returns the metadata for another registered client. * * @param clientId public id for another registered client * @return metadata map */ Metadata getMetadata( String clientId ) throws SampException; /** * Declares this registered client's MType subscriptions. * *

Only permitted if this client is already callable. * * @param subs {@link org.astrogrid.samp.Subscriptions}-like map */ void declareSubscriptions( Map subs ) throws SampException; /** * Returns the subscriptions for another registered client. * * @param clientId public id for another registered client * @return subscriptions map */ Subscriptions getSubscriptions( String clientId ) throws SampException; /** * Returns the list of client public IDs for those clients currently * registered. * * @return array of client ids, excluding the one for this client */ String[] getRegisteredClients() throws SampException; /** * Returns a map of subscriptions for a given MType. * * @param mtype MType * @return map in which the keys are the public IDs of clients subscribed * to mtype */ Map getSubscribedClients( String mtype ) throws SampException; /** * Sends a message to a given client without wanting a response. * * @param recipientId public-id of client to receive message * @param msg {@link org.astrogrid.samp.Message}-like map */ void notify( String recipientId, Map msg ) throws SampException; /** * Sends a message to all subscribed clients without wanting a response. * * @param msg {@link org.astrogrid.samp.Message}-like map * @return list of public-ids for clients to which the notify will be sent */ List notifyAll( Map msg ) throws SampException; /** * Sends a message to a given client expecting a response. * The receiveResponse method of this connection's * {@link CallableClient} will be called with a * response at some time in the future. * *

Only permitted if this client is already callable. * * @param recipientId public-id of client to receive message * @param msgTag arbitrary string tagging this message for caller's * benefit * @param msg {@link org.astrogrid.samp.Message}-like map * @return message ID */ String call( String recipientId, String msgTag, Map msg ) throws SampException; /** * Sends a message to all subscribed clients expecting responses. * The receiveResponse method of this connection's * {@link CallableClient} will be called with responses at some * time in the future. * *

Only permitted if this client is already callable. * * @param msgTag arbitrary string tagging this message for caller's * benefit * @param msg {@link org.astrogrid.samp.Message}-like map * @return public-id->msg-id map for clients to which an attempt to * send the call will be made */ Map callAll( String msgTag, Map msg ) throws SampException; /** * Sends a message synchronously to a client, waiting for the response. * If more seconds elapse than the value of the timeout * parameter, an exception will result. * * @param recipientId public-id of client to receive message * @param msg {@link org.astrogrid.samp.Message}-like map * @param timeout timeout in seconds, or <0 for no timeout * @return response */ Response callAndWait( String recipientId, Map msg, int timeout ) throws SampException; /** * Supplies a response to a previously received message. * * @param msgId ID associated with earlier send * @param response {@link org.astrogrid.samp.Response}-like map */ void reply( String msgId, Map response ) throws SampException; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/HubConnector.java000066400000000000000000001365161356450004300257270ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.Timer; import java.util.TimerTask; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.Client; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Platform; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.Subscriptions; /** * Manages a client's connection to SAMP hubs. * Normally SAMP client applications will use one instance of this class * for as long as they are running. * It provides the following services: *

    *
  • Keeps track of other registered clients *
  • Keeps track of hubs shutting down and starting up *
  • Manages client metadata and subscription information across * hub reconnections *
  • Facilitates provision of callback services by the client *
  • Implements simple MTypes such as samp.app.ping. *
  • Optionally looks out for hubs starting up and connects automatically * when they do *
* *

This object provides a {@link #getConnection} method which provides * the currently active {@link HubConnection} object if one exists or can be * acquired. The HubConnection can be used for direct calls * on the running hub, but in some cases similar methods with additional * functionality exist in this class: *

*
{@link #declareMetadata declareMetadata} *
{@link #declareSubscriptions declareSubscriptions} *
These methods not only make the relevant declarations to the * existing hub connection, if one exists, but will retain the * metadata and subscriptions information and declare them to * other connections if the hub connection is terminated and * restarted (with either the same or a different hub) * over the lifetime of this object. *
*
{@link #callAndWait callAndWait} *
Provides identical semantics to the similarly named * HubConnection method, but communicates with the hub * asynchronously and fakes the synchrony at the client end. * This is more robust and almost certainly a better idea. *
*
{@link #call call} *
{@link #callAll callAll} *
Convenience methods to make asynchronous calls without having to * worry about registering handlers which match up message tags. *
* *
* *

It is good practice to call {@link #setActive setActive(false)} * when this object is finished with; however if it is not called * explicitly, any open connection will unregister itself on * object finalisation or JVM termination, as long as the JVM shuts * down cleanly. * *

Examples

* Here is an example of what use of this class might look like: *
 *   // Construct a connector
 *   ClientProfile profile = DefaultClientProfile.getProfile();
 *   HubConnector conn = new HubConnector(profile)
 *
 *   // Configure it with metadata about this application
 *   Metadata meta = new Metadata();
 *   meta.setName("Foo");
 *   meta.setDescriptionText("Application that does stuff");
 *   conn.declareMetadata(meta);
 *
 *   // Prepare to receive messages with specific MType(s)
 *   conn.addMessageHandler(new AbstractMessageHandler("stuff.do") {
 *       public Map processCall(HubConnection c, String senderId, Message msg) {
 *           // do stuff
 *       }
 *   });
 *
 *   // This step required even if no custom message handlers added.
 *   conn.declareSubscriptions(conn.computeSubscriptions());
 *
 *   // Keep a look out for hubs if initial one shuts down
 *   conn.setAutoconnect(10);
 *
 *   // Broadcast a message
 *   conn.getConnection().notifyAll(new Message("stuff.event.doing"));
 * 
* *

A real example, including use of the GUI hooks, can be found in the * {@link org.astrogrid.samp.gui.HubMonitor} client source code. * *

Backwards Compatibility Note

* This class does less than it did in earlier versions; * the functionality which is no longer here can now be found in the * {@link org.astrogrid.samp.gui.GuiHubConnector} class instead. * * @author Mark Taylor * @since 15 Jul 2008 */ public class HubConnector { private final ClientProfile profile_; private final TrackedClientSet clientSet_; private final List messageHandlerList_; private final List responseHandlerList_; private final ConnectorCallableClient callable_; private final Map responseMap_; private final ClientTracker clientTracker_; private final CallHandler callHandler_; private volatile boolean isActive_; private volatile HubConnection connection_; private volatile Metadata metadata_; private volatile Subscriptions subscriptions_; private volatile int autoSec_; private volatile Timer regTimer_; private volatile int iCall_; private final Logger logger_ = Logger.getLogger( HubConnector.class.getName() ); private static final String SHUTDOWN_MTYPE = "samp.hub.event.shutdown"; private static final String DISCONNECT_MTYPE = "samp.hub.disconnect"; private static final String PING_MTYPE = "samp.app.ping"; private static final String GETENV_MTYPE = "client.env.get"; /** * Constructs a HubConnector based on a given profile instance. * A default client set implementation is used. * * @param profile profile implementation */ public HubConnector( ClientProfile profile ) { this( profile, new TrackedClientSet() ); } /** * Constructs a HubConnector based on a given profile instance * using a custom client set implementation. * * @param profile profile implementation * @param clientSet object to keep track of registered clients */ public HubConnector( ClientProfile profile, TrackedClientSet clientSet ) { profile_ = profile; clientSet_ = clientSet; isActive_ = true; // Set up data structures. messageHandlerList_ = Collections.synchronizedList( new ArrayList() ); responseHandlerList_ = Collections.synchronizedList( new ArrayList() ); callable_ = new ConnectorCallableClient(); responseMap_ = Collections.synchronizedMap( new HashMap() ); // Listen out for events describing changes to registered clients. clientTracker_ = new ClientTracker( clientSet_ ); addMessageHandler( clientTracker_ ); // Listen out for hub shutdown events. addMessageHandler( new AbstractMessageHandler( SHUTDOWN_MTYPE ) { public Map processCall( HubConnection connection, String senderId, Message message ) { String mtype = message.getMType(); assert SHUTDOWN_MTYPE.equals( mtype ); checkHubMessage( connection, senderId, mtype ); disconnect(); return null; } } ); // Listen out for forcible disconnection events. addMessageHandler( new AbstractMessageHandler( DISCONNECT_MTYPE ) { public Map processCall( HubConnection connection, String senderId, Message message ) { String mtype = message.getMType(); assert DISCONNECT_MTYPE.equals( mtype ); if ( senderId.equals( connection.getRegInfo().getHubId() ) ) { Object reason = message.getParam( "reason" ); logger_.warning( "Forcible disconnect from hub" + ( reason == null ? " [no reason given]" : " (" + reason + ")" ) ); disconnect(); isActive_ = false; return null; } else { throw new IllegalArgumentException( "Ignoring " + mtype + " message from non-hub" + " client " + senderId ); } } } ); // Implement samp.app.ping MType. addMessageHandler( new AbstractMessageHandler( PING_MTYPE ) { public Map processCall( HubConnection connection, String senderId, Message message ) throws InterruptedException { String waitMillis = (String) message.getParam( "waitMillis" ); if ( waitMillis != null ) { Object lock = new Object(); synchronized ( lock ) { lock.wait( SampUtils.decodeInt( waitMillis ) ); } } return null; } } ); // Implement client.env.get MType. addMessageHandler( new AbstractMessageHandler( GETENV_MTYPE ) { public Map processCall( HubConnection connection, String senderId, Message message ) { String name = (String) message.getParam( "name" ); String value = Platform.getPlatform().getEnv( name ); Map result = new HashMap(); result.put( "value", value == null ? "" : value ); return result; }; } ); // Listen out for responses to calls for which we are providing // faked synchronous call behaviour. addResponseHandler( new ResponseHandler() { public boolean ownsTag( String msgTag ) { return responseMap_.containsKey( msgTag ); } public void receiveResponse( HubConnection connection, String responderId, String msgTag, Response response ) { synchronized ( responseMap_ ) { if ( responseMap_.containsKey( msgTag ) && responseMap_.get( msgTag ) == null ) { responseMap_.put( msgTag, response ); responseMap_.notifyAll(); } } } } ); // Listen out for responses to calls for which we have agreed to // pass results to user-supplied ResultHandler objects. callHandler_ = new CallHandler(); addResponseHandler( callHandler_ ); } /** * Sets the interval at which this connector attempts to connect to a * hub if no connection currently exists. * Otherwise, a connection will be attempted whenever * {@link #getConnection} is called. * * @param autoSec number of seconds between attempts; * <=0 means no automatic connections are attempted */ public synchronized void setAutoconnect( int autoSec ) { autoSec_ = autoSec; configureRegisterTimer( autoSec_ ); } /** * Configures a timer thread to attempt registration periodically. * * @param autoSec number of seconds between attempts; * <=0 means no automatic connections are attempted */ private synchronized void configureRegisterTimer( int autoSec ) { // Cancel and remove any existing auto-connection timer. if ( regTimer_ != null ) { regTimer_.cancel(); regTimer_ = null; } // If required, install a new one. if ( autoSec > 0 ) { TimerTask regTask = new TimerTask() { public void run() { if ( ! isConnected() ) { try { HubConnection conn = getConnection(); if ( conn == null ) { logger_.config( "SAMP autoconnection attempt " + "failed" ); } else { logger_.info( "SAMP autoconnection attempt " + "succeeded" ); } } catch ( SampException e ) { logger_.config( "SAMP Autoconnection attempt " + " failed: " + e ); } } } }; regTimer_ = new Timer( true ); regTimer_.schedule( regTask, 0, autoSec_ * 1000 ); } } /** * Declares the metadata for this client. * This declaration affects the current connection and any future ones. * * @param meta {@link org.astrogrid.samp.Metadata}-like map */ public void declareMetadata( Map meta ) { Metadata md = Metadata.asMetadata( meta ); md.check(); metadata_ = md; if ( isConnected() ) { try { connection_.declareMetadata( md ); } catch ( SampException e ) { logger_.log( Level.WARNING, "SAMP metadata declaration failed", e ); } } } /** * Returns this client's own metadata. * * @return metadata */ public Metadata getMetadata() { return metadata_; } /** * Declares the MType subscriptions for this client. * This declaration affects the current connection and any future ones. * *

Note that this call must be made, with a subscription list * which includes the various hub administrative messages, in order * for this connector to act on those messages (for instance to * update its client map and so on). For this reason, it is usual * to call it with the subs argument given by * the result of calling {@link #computeSubscriptions}. * * @param subscriptions {@link org.astrogrid.samp.Subscriptions}-like map */ public void declareSubscriptions( Map subscriptions ) { Subscriptions subs = Subscriptions.asSubscriptions( subscriptions ); subs.check(); subscriptions_ = subs; if ( isConnected() ) { try { connection_.declareSubscriptions( subs ); } catch ( SampException e ) { logger_.log( Level.WARNING, "Subscriptions declaration failed", e ); } } } /** * Returns this client's own subscriptions. * * @return subscriptions */ public Subscriptions getSubscriptions() { return subscriptions_; } /** * Works out the subscriptions map for this connector. * This is based on the subscriptions declared by for any * {@link MessageHandler}s installed in this connector as well as * any MTypes which this connector implements internally. * The result of this method is usually a suitable value to pass * to {@link #declareSubscriptions}. However you might wish to * remove some entries from the result if there are temporarily * unsubscribed services. * * @return subscription list for MTypes apparently implemented by this * connector */ public Subscriptions computeSubscriptions() { MessageHandler[] mhandlers = (MessageHandler[]) messageHandlerList_.toArray( new MessageHandler[ 0 ] ); Map subs = new HashMap(); for ( int ih = mhandlers.length - 1; ih >= 0; ih-- ) { subs.putAll( mhandlers[ ih ].getSubscriptions() ); } return Subscriptions.asSubscriptions( subs ); } /** * Adds a MessageHandler to this connector, which allows it to respond * to incoming messages. * Note that this does not in itself update the list of subscriptions * for this connector; you may want to follow it with a call to *

     *    declareSubscriptions(computeSubscriptions());
     * 
* * @param handler handler to add */ public void addMessageHandler( MessageHandler handler ) { messageHandlerList_.add( handler ); } /** * Removes a previously-added MessageHandler to this connector. * Note that this does not in itself update the list of subscriptions * for this connector; you may want to follow it with a call to *
     *    declareSubscriptions(computeSubscriptions());
     * 
* * @param handler handler to remove */ public void removeMessageHandler( MessageHandler handler ) { messageHandlerList_.remove( handler ); } /** * Adds a ResponseHandler to this connector, which allows it to receive * replies from messages sent asynchronously. * *

Note however that this class's {@link #callAndWait callAndWait} method * can provide a synchronous facade for fully asynchronous messaging, * which in many cases will be more convenient than installing your * own response handlers to deal with asynchronous replies. * * @param handler handler to add */ public void addResponseHandler( ResponseHandler handler ) { responseHandlerList_.add( handler ); } /** * Removes a ResponseHandler from this connector. * * @param handler handler to remove */ public void removeResponseHandler( ResponseHandler handler ) { responseHandlerList_.remove( handler ); } /** * Sets whether this connector is active or not. * If set false, any existing connection will be terminated (the client * will unregister) and autoconnection attempts will be suspended. * If set true, if there is no existing connection an attempt will be * made to register, and autoconnection attempts will begin if applicable. * * @param active whether this connector should be active * @see #setAutoconnect */ public void setActive( boolean active ) { isActive_ = active; if ( active ) { if ( connection_ == null ) { try { getConnection(); } catch ( SampException e ) { logger_.log( Level.WARNING, "Hub connection attempt failed", e ); } } configureRegisterTimer( autoSec_ ); } else { HubConnection connection = connection_; if ( connection != null ) { disconnect(); try { connection.unregister(); } catch ( SampException e ) { logger_.log( Level.INFO, "Unregister attempt failed", e ); } } configureRegisterTimer( 0 ); } } /** * Indicates whether this connector is active or not. * * @return true if this connector is willing to connect */ public boolean isActive() { return isActive_; } /** * Sends a message synchronously to a client, waiting for the response. * If more seconds elapse than the value of the timeout * parameter, an exception will result. * *

The semantics of this call are, as far as the caller is concerned, * identical to that of the similarly named {@link HubConnection} method. * However, in this case the client communicates with the hub * asynchronously and internally simulates the synchrony for the caller, * rather than letting the hub do that. * This is more robust and almost certainly a better idea. * * @param recipientId public-id of client to receive message * @param msg {@link org.astrogrid.samp.Message}-like map * @param timeout timeout in seconds, or <=0 for no timeout * @return response */ public Response callAndWait( String recipientId, Map msg, int timeout ) throws SampException { long finish = timeout > 0 ? System.currentTimeMillis() + timeout * 1000 : Long.MAX_VALUE; // 3e8 years HubConnection connection = getConnection(); String msgTag = createTag( this ); responseMap_.put( msgTag, null ); connection.call( recipientId, msgTag, msg ); synchronized ( responseMap_ ) { while ( responseMap_.containsKey( msgTag ) && responseMap_.get( msgTag ) == null && System.currentTimeMillis() < finish ) { long millis = finish - System.currentTimeMillis(); if ( millis > 0 ) { try { responseMap_.wait( millis ); } catch ( InterruptedException e ) { throw new SampException( "Wait interrupted", e ); } } } if ( responseMap_.containsKey( msgTag ) ) { Response response = (Response) responseMap_.remove( msgTag ); if ( response != null ) { return response; } else { assert System.currentTimeMillis() >= finish; throw new SampException( "Synchronous call timeout" ); } } else { if ( connection != connection_ ) { throw new SampException( "Hub connection lost" ); } else { throw new AssertionError(); } } } } /** * Sends a message asynchronously to a single client, making a callback * on a supplied ResultHandler object when the result arrives. * The {@link org.astrogrid.samp.client.ResultHandler#done} method will * be called after the result has arrived or the timeout elapses, * whichever happens first. * *

This convenience method allows the user to make an asynchronous * call without having to worry registering message handlers and * matching message tags. * * @param recipientId public-id of client to receive message * @param msg {@link org.astrogrid.samp.Message}-like map * @param resultHandler object called back when response arrives or * timeout is exceeded * @param timeout timeout in seconds, or <=0 for no timeout */ public void call( String recipientId, Map msg, ResultHandler resultHandler, int timeout ) throws SampException { HubConnection connection = getConnection(); if ( connection == null ) { throw new SampException( "Not connected" ); } String tag = createTag( this ); callHandler_.registerHandler( tag, resultHandler, timeout ); try { connection.call( recipientId, tag, msg ); callHandler_.setRecipients( tag, new String[] { recipientId, } ); } catch ( SampException e ) { callHandler_.unregisterHandler( tag ); throw e; } } /** * Sends a message asynchronously to all subscribed clients, * making callbacks on a supplied ResultHandler object when the * results arrive. * The {@link org.astrogrid.samp.client.ResultHandler#done} method will * be called after all the results have arrived or the timeout elapses, * whichever happens first. * *

This convenience method allows the user to make an asynchronous * call without having to worry registering message handlers and * matching message tags. * * @param msg {@link org.astrogrid.samp.Message}-like map * @param resultHandler object called back when response arrives or * timeout is exceeded * @param timeout timeout in seconds, or <=0 for no timeout */ public void callAll( Map msg, ResultHandler resultHandler, int timeout ) throws SampException { HubConnection connection = getConnection(); if ( connection == null ) { throw new SampException( "Not connected" ); } String tag = createTag( this ); callHandler_.registerHandler( tag, resultHandler, timeout ); try { Map callMap = connection.callAll( tag, msg ); callHandler_.setRecipients( tag, (String[]) callMap.keySet() .toArray( new String[ 0 ] ) ); } catch ( SampException e ) { callHandler_.unregisterHandler( tag ); throw e; } } /** * Indicates whether this connector is currently registered with a * running hub. * If true, the result of {@link #getConnection} will be non-null. * * @return true if currently connected to a hub */ public boolean isConnected() { return connection_ != null; } /** * If necessary attempts to acquire, and returns, a connection to a * running hub. * If there is an existing connection representing a registration * with a hub, it is returned. If not, and this connector is active, * an attempt is made to connect and register, followed by a call to * {@link #configureConnection configureConnection}, is made. * *

Note that if {@link #setActive setActive(false)} has been called, * null will be returned. * * @return hub connection representing configured registration with a hub * if a hub is running; if not, null * @throws SampException in the case of some unexpected error */ public HubConnection getConnection() throws SampException { HubConnection connection = connection_; if ( connection == null && isActive_ ) { connection = createConnection(); if ( connection != null ) { connection_ = connection; configureConnection( connection ); clientTracker_.initialise( connection ); connectionChanged( true ); } } return connection; } /** * Configures a connection with a hub in accordance with the state of * this object. * The hub is made aware of how to perform callbacks on the registered * client, and any current metadata and subscriptions are declared. * * @param connection connection representing registration with a hub */ public void configureConnection( HubConnection connection ) throws SampException { if ( metadata_ != null ) { connection.declareMetadata( metadata_ ); } if ( callable_ != null ) { connection.setCallable( callable_ ); callable_.setConnection( connection ); if ( subscriptions_ != null ) { connection.declareSubscriptions( subscriptions_ ); } } } /** * Returns a map which keeps track of other clients currently registered * with the hub to which this object is connected, including their * currently declared metadata and subscriptions. * Map keys are public IDs and values are * {@link org.astrogrid.samp.Client}s. * *

This map is {@link java.util.Collections#synchronizedMap synchronized} * which means that to iterate over any of its views * you must synchronize on it. * When the map or any of its contents changes, it will receive a * {@link java.lang.Object#notifyAll}. * *

To keep itself up to date, the client map reads hub status messages. * These will only be received if * declareSubscriptions(computeSubscriptions()) has been * called. * Hence, this method should only be called after * {@link #declareSubscriptions} has been called. * If this order is not observed, a warning will be emitted through * the logging system. * * @return id->Client map */ public Map getClientMap() { if ( subscriptions_ == null ) { logger_.warning( "Danger: you should call declareSubscriptions " + "before using client map" ); } return getClientSet().getClientMap(); } /** * Returns the tracked client set implementation which is used to keep * track of the currently registered clients. * * @return client set implementation */ protected TrackedClientSet getClientSet() { return clientSet_; } /** * Invoked by this class to create a hub connection. * The default implementation just calls profile.register(). * * @return new hub connection */ protected HubConnection createConnection() throws SampException { return profile_.register(); } /** * Unregisters from the currently connected hub, if any. * Performs any associated required cleanup. */ protected void disconnect() { boolean wasConnected = connection_ != null; connection_ = null; clientTracker_.clear(); callHandler_.stopTimeouter(); synchronized ( responseMap_ ) { responseMap_.clear(); responseMap_.notifyAll(); } if ( wasConnected ) { connectionChanged( false ); } } /** * Method which is called every time this connector changes its connection * status (from disconnected to connected, or vice versa). * The default implementation does nothing, but it may be overridden * by subclasses wishing to be informed of these events. * * @param isConnected true if we've just registered; * false if we've just unregistered */ protected void connectionChanged( boolean isConnected ) { } /** * Performs sanity checking on a message which is normally expected to * be sent only by the hub client itself. * * @param connection connection to the hub * @param senderId public client id of sender * @param mtype MType of sent message */ private void checkHubMessage( HubConnection connection, String senderId, String mtype ) { if ( ! senderId.equals( connection.getRegInfo().getHubId() ) ) { logger_.warning( "Hub admin message " + mtype + " received from " + "non-hub client. Acting on it anyhow" ); } } /** * Generates a new msgTag for use with this connector. * It is guaranteed to return a different value on each invocation. * It is advisable to use this method whenever a message tag is required * to prevent clashes. * * @param owner object to identify caller * (not really necessary - may be null) * @return unique tag for this connector */ public synchronized String createTag( Object owner ) { return ( owner == null ? "tag" : ( String.valueOf( owner ) + ":" ) ) + ++iCall_; } /** * CallableClient implementation used by this class. */ private class ConnectorCallableClient implements CallableClient { private HubConnection conn_; /** * Sets the currently active hub connection. * * @param connection connection */ private void setConnection( HubConnection connection ) { conn_ = connection; } public void receiveNotification( String senderId, Message message ) { // Offer the notification to each registered MessageHandler in turn. // It may in principle get processed by more than one. // This is almost certainly harmless. MessageHandler[] mhandlers = (MessageHandler[]) messageHandlerList_.toArray( new MessageHandler[ 0 ] ); for ( int ih = 0; ih < mhandlers.length; ih++ ) { MessageHandler handler = mhandlers[ ih ]; Subscriptions subs = Subscriptions.asSubscriptions( handler.getSubscriptions() ); String mtype = message.getMType(); if ( subs.isSubscribed( mtype ) ) { try { handler.receiveNotification( conn_, senderId, message ); } catch ( Throwable e ) { logger_.log( Level.WARNING, "Notify handler failed " + mtype, e ); } } } } public void receiveCall( String senderId, String msgId, Message message ) { // Offer the call to each registered MessageHandler in turn. // Since only one should be allowed to respond to it, only // the first one which bites is allowed to process it. String mtype = message.getMType(); ErrInfo errInfo = null; MessageHandler[] mhandlers = (MessageHandler[]) messageHandlerList_.toArray( new MessageHandler[ 0 ] ); for ( int ih = 0; ih < mhandlers.length; ih++ ) { MessageHandler handler = mhandlers[ ih ]; Subscriptions subs = Subscriptions.asSubscriptions( handler.getSubscriptions() ); if ( subs.isSubscribed( mtype ) ) { try { handler.receiveCall( conn_, senderId, msgId, message ); return; } catch ( Throwable e ) { errInfo = new ErrInfo( e ); logger_.log( Level.WARNING, "Call handler failed " + mtype, e ); } } } if ( errInfo == null ) { logger_.warning( "No handler for subscribed MType " + mtype ); errInfo = new ErrInfo( "No handler found" ); errInfo.setUsertxt( "No handler was found for the supplied" + " MType. " + "Looks like a programming error " + "at the recipient end. Sorry." ); } Response response = Response.createErrorResponse( errInfo ); response.check(); try { conn_.reply( msgId, response ); } catch ( SampException e ) { logger_.warning( "Failed to reply to " + msgId ); } } public void receiveResponse( String responderId, String msgTag, Response response ) { // Offer the response to each registered ResponseHandler in turn. // It shouldn't be processed by more than one, but if it is, // warn about it. int handleCount = 0; ResponseHandler[] rhandlers = (ResponseHandler[]) responseHandlerList_.toArray( new ResponseHandler[ 0 ] ); for ( int ih = 0; ih < rhandlers.length; ih++ ) { ResponseHandler handler = rhandlers[ ih ]; if ( handler.ownsTag( msgTag ) ) { handleCount++; try { handler.receiveResponse( conn_, responderId, msgTag, response ); } catch ( Exception e ) { logger_.log( Level.WARNING, "Response handler failed", e ); } } } if ( handleCount == 0 ) { logger_.warning( "No handler for message " + msgTag + " response" ); } else if ( handleCount > 1 ) { logger_.warning( "Multiple (" + handleCount + ")" + " handlers handled message " + msgTag + " respose" ); } } } /** * ResponseHandler which looks after responses made by calls using the * call() and callAll() convenience methods. */ private class CallHandler implements ResponseHandler { private final SortedMap tagMap_; private Thread timeouter_; /** * Constructor. */ CallHandler() { // Set up a structure to contain tag->CallItem entries for // responses we are expecting. They are arranged in order of // which is going to time out soonest. tagMap_ = new TreeMap(); } /** * Ensures that a thread is running to wake up when the next timeout * has (or at least might have) happened. */ private void readyTimeouter() { synchronized ( tagMap_ ) { if ( timeouter_ == null ) { timeouter_ = new Thread( "ResultHandler timeout watcher" ) { public void run() { watchTimeouts(); } }; timeouter_.setDaemon( true ); timeouter_.start(); } } } /** * Stops any current timeout watcher operating on behalf of this * handler and tidies up associated resources. */ private void stopTimeouter() { synchronized ( tagMap_ ) { if ( timeouter_ != null ) { timeouter_.interrupt(); } timeouter_ = null; tagMap_.clear(); } } /** * Runs in a daemon thread to watch out for timeouts that might * have occurred. */ private void watchTimeouts() { while ( ! Thread.currentThread().isInterrupted() ) { synchronized ( tagMap_ ) { // Wait until the next scheduled timeout is expected. long nextFinish = tagMap_.isEmpty() ? Long.MAX_VALUE : ((CallItem) tagMap_.get( tagMap_.firstKey() )) .finish_; final long delay = nextFinish - System.currentTimeMillis(); if ( delay > 0 ) { try { tagMap_.wait( delay ); } catch ( InterruptedException e ) { Thread.currentThread().interrupt(); } } // Then process any timeouts that are pending. long now = System.currentTimeMillis(); for ( Iterator it = tagMap_.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); CallItem item = (CallItem) entry.getValue(); if ( now >= item.finish_ ) { item.handler_.done(); it.remove(); } } } } } /** * Stores a ResultHandler object which will take delivery of the * responses tagged with a given tag. * * @param tag message tag identifying send/response * @param handler callback object * @param timeout milliseconds before forcing completion */ public void registerHandler( String tag, ResultHandler handler, int timeout ) { long finish = timeout > 0 ? System.currentTimeMillis() + timeout * 1000 : Long.MAX_VALUE; // 3e8 years CallItem item = new CallItem( handler, finish ); if ( ! item.isDone() ) { synchronized ( tagMap_ ) { readyTimeouter(); tagMap_.put( tag, item ); tagMap_.notifyAll(); } } else { handler.done(); } } /** * Set the recipients from which we are expecting responses. * Once all are in, the handler can be disposed of. * * @param tag message tag identifying send/response * @param recipients clients expected to reply */ public void setRecipients( String tag, String[] recipients ) { CallItem item; synchronized ( tagMap_ ) { item = (CallItem) tagMap_.get( tag ); } item.setRecipients( recipients ); retireIfDone( tag, item ); } /** * Unregister a handler for which no responses are expected. * * @param tag message tag identifying send/response */ public void unregisterHandler( String tag ) { synchronized ( tagMap_ ) { tagMap_.remove( tag ); } } public boolean ownsTag( String tag ) { synchronized ( tagMap_ ) { return tagMap_.containsKey( tag ); } } public void receiveResponse( HubConnection connection, String responderId, String msgTag, Response response ) { final CallItem item; synchronized ( tagMap_ ) { item = (CallItem) tagMap_.get( msgTag ); } if ( item != null ) { item.addResponse( responderId, response ); retireIfDone( msgTag, item ); } } /** * Called when a tag/handler entry might be ready to finish with. */ private void retireIfDone( String tag, CallItem item ) { if ( item.isDone() ) { synchronized ( tagMap_ ) { item.handler_.done(); tagMap_.remove( tag ); } } } } /** * Stores state about a particular set of responses expected by the * CallHandler class. */ private class CallItem implements Comparable { final ResultHandler handler_; final long finish_; volatile Map responseMap_; // responderId -> Response volatile Map recipientMap_; // responderId -> Client /** * Constructor. * * @param handler callback object * @param finish epoch at which timeout should be called */ CallItem( ResultHandler handler, long finish ) { handler_ = handler; finish_ = finish; } /** * Sets the recipient Ids for which responses are expected. * * @param recipientIds recipient client ids */ public synchronized void setRecipients( String[] recipientIds ) { recipientMap_ = new HashMap(); // Store client objects for each recipient ID. Note however // because of various synchrony issues we can't guarantee that // all these client objects can be determined - some may be null. // Store the ids as keys in any case. Map clientMap = getClientMap(); for ( int ir = 0; ir < recipientIds.length; ir++ ) { String id = recipientIds[ ir ]; Client client = (Client) clientMap.get( id ); recipientMap_.put( id, client ); } // If we have pending responses (couldn't be processed earlier // because no recipients), take care of them now. if ( responseMap_ != null ) { for ( Iterator it = responseMap_.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); String responderId = (String) entry.getKey(); Response response = (Response) entry.getValue(); processResponse( responderId, response ); } responseMap_ = null; } } /** * Take delivery of a response object. * * @param responderId client ID of responder * @param response response object */ public synchronized void addResponse( String responderId, Response response ) { // If we know the recipients, deal with it now. if ( recipientMap_ != null ) { processResponse( responderId, response ); } // Otherwise, defer until we do know the recipients. else { if ( responseMap_ == null ) { responseMap_ = new HashMap(); } responseMap_.put( responderId, response ); } } /** * Process a response when we have both the list of recipients * and the response itself. * * @param responderId client ID of responder * @param response response object */ private synchronized void processResponse( final String responderId, Response response ) { if ( recipientMap_.containsKey( responderId ) ) { // Get a client object. We have to try belt and braces. Client client = (Client) recipientMap_.get( responderId ); if ( client == null ) { client = (Client) getClientMap().get( responderId ); } if ( client == null ) { client = new Client() { public String getId() { return responderId; } public Metadata getMetadata() { return null; } public Subscriptions getSubscriptions() { return null; } }; } // Make the callback to the supplied handler. handler_.result( client, response ); // Note that we've done this one. recipientMap_.remove( responderId ); } } /** * Indicate whether this call item has received all the responses it's * going to. * * @return iff no further activity is expected */ public synchronized boolean isDone() { return ( recipientMap_ != null && recipientMap_.isEmpty() ) || System.currentTimeMillis() >= finish_; } /** * Compares on timeout epochs. * Implementation is consistent with equals, * which means it's OK to use them in a SortedMap. */ public int compareTo( Object o ) { CallItem other = (CallItem) o; if ( this.finish_ < other.finish_ ) { return -1; } else if ( this.finish_ > other.finish_ ) { return +1; } else { return System.identityHashCode( this ) - System.identityHashCode( other ); } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/LogResultHandler.java000066400000000000000000000027271356450004300265500ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.util.logging.Logger; import org.astrogrid.samp.Client; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.HubConnection; /** * ResultHandler implementation which outputs some information about * responses received through the logging system. * * @author Mark Taylor * @since 12 Nov 2008 */ public class LogResultHandler implements ResultHandler { private final String mtype_; private static final Logger logger_ = Logger.getLogger( LogResultHandler.class.getName() ); /** * Constructor. * * @param msg message which was sent */ public LogResultHandler( Message msg ) { mtype_ = msg.getMType(); } public void result( Client client, Response response ) { if ( response.isOK() ) { logger_.info( mtype_ + ": successful send to " + client ); } else { logger_.warning( mtype_ + ": error sending to " + client ); ErrInfo errInfo = response.getErrInfo(); if ( errInfo != null ) { String errortxt = errInfo.getErrortxt(); if ( errortxt != null ) { logger_.warning( errortxt ); } logger_.info( SampUtils.formatObject( errInfo, 3 ) ); } } } public void done() { } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/MessageHandler.java000066400000000000000000000032161356450004300262060ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.util.Map; import org.astrogrid.samp.Message; /** * Interface for a client which wishes to receive messages. * In most cases it is more convenient to subclass the abstract class * {@link AbstractMessageHandler} than to implement this interface directly. * * @author Mark Taylor * @since 16 Jul 2008 */ public interface MessageHandler { /** * Returns a Subscriptions map corresponding to the messages * handled by this object. * Only messages with MTypes which match the keys of this map will * be passed to this object. * * @return {@link org.astrogrid.samp.Subscriptions}-like map */ Map getSubscriptions(); /** * Processes a message which does not require a response. * * @param connection hub connection * @param senderId public ID of client which sent the message * @param message message */ void receiveNotification( HubConnection connection, String senderId, Message message ) throws Exception; /** * Processes a message which does require a response. * Implementations should make sure that a subsequent call to * connection.reply() is made using the * supplied msgId. * * @param connection hub connection * @param senderId public ID of client which sent the message * @param msgId message ID * @param message message */ void receiveCall( HubConnection connection, String senderId, String msgId, Message message ) throws Exception; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/ResponseHandler.java000066400000000000000000000023211356450004300264140ustar00rootroot00000000000000package org.astrogrid.samp.client; import org.astrogrid.samp.Response; /** * Interface for a client which wishes to receive responses to message it * has sent asynchrnonously using call or callAll. * * @author Mark Taylor * @since 16 Jul 2008 */ public interface ResponseHandler { /** * Indicates whether this handler will process the response with a * given message tag. * * @param msgTag tag with which earlier call was labelled * @return true iff this handler wants to process the response labelled * with msgTag */ boolean ownsTag( String msgTag ); /** * Processes a response to an earlier message. * Will only be called for msgTag values which return * true from {@link #ownsTag}. * * @param connection hub connection * @param responderId client id of client sending response * @param msgTag message tag from previous call * @param response response object */ void receiveResponse( HubConnection connection, String responderId, String msgTag, Response response ) throws Exception; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/ResultHandler.java000066400000000000000000000013561356450004300261030ustar00rootroot00000000000000package org.astrogrid.samp.client; import org.astrogrid.samp.Client; import org.astrogrid.samp.Response; /** * Interface which consumes call responses. * * @author Mark Taylor * @since 12 Nov 2008 */ public interface ResultHandler { /** * Called when a response is received from a client to which the message * was sent. * * @param responder responder client * @param response content of response */ public void result( Client responder, Response response ); /** * Called when no more {@link #result} invocations will be made, * either because all have been received or for some other reason, * such as a timeout or the hub shutting down. */ public void done(); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/SampException.java000066400000000000000000000020771356450004300261070ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.io.IOException; /** * Exception thrown when some error occurs in SAMP processing. * Note that this is a subclass of {@link java.io.IOException}. * * @author Mark Taylor * @since 15 Jul 2008 */ public class SampException extends IOException { /** * Constructs an exception with no message. */ public SampException() { super(); } /** * Consructs an exception with a given message. * * @param msg message */ public SampException( String msg ) { super( msg ); } /** * Constructs an exception with a given cause. * * @param cause cause of this exception */ public SampException( Throwable cause ) { this(); initCause( cause ); } /** * Constructs an exception with a given message and cause. * * @param msg message * @param cause cause of this exception */ public SampException( String msg, Throwable cause ) { this( msg ); initCause( cause ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/TrackedClientSet.java000066400000000000000000000066201356450004300265160ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.astrogrid.samp.Client; /** * Collection of Client objects which can be notified and interrogated * about the clients which are currently registered. * Instances of this class are thread-safe. * * @author Mark Taylor * @since 25 Nov 2008 */ public class TrackedClientSet { private final Map clientMap_; private final Map clientMapView_; /** * Constructor. */ public TrackedClientSet() { clientMap_ = new HashMap(); clientMapView_ = Collections.synchronizedMap( Collections .unmodifiableMap( clientMap_ ) ); } /** * Adds a client to this model. * Listeners are informed. May be called from any thread. * * @param client client to add */ public void addClient( Client client ) { synchronized ( clientMapView_ ) { clientMap_.put( client.getId(), client ); clientMapView_.notifyAll(); } } /** * Removes a client from this model. * Listeners are informed. May be called from any thread. * * @param client client to remove */ public synchronized void removeClient( Client client ) { Client c; synchronized ( clientMapView_ ) { c = (Client) clientMap_.remove( client.getId() ); clientMapView_.notifyAll(); } boolean removed = c != null; if ( ! removed ) { throw new IllegalArgumentException( "No such client " + client ); } assert client.equals( c ); } /** * Sets the contents of this model to a given list. * Listeners are informed. May be called from any thread. * * @param clients current client list */ public synchronized void setClients( Client[] clients ) { synchronized ( clientMapView_ ) { clientMap_.clear(); for ( int ic = 0; ic < clients.length; ic++ ) { Client client = clients[ ic ]; clientMap_.put( client.getId(), client ); } clientMapView_.notifyAll(); } } /** * Notifies listeners that a given client's attributes (may) have * changed. May be called from any thread. * * @param client modified client * @param metaChanged true if metadata may have changed * (false if known unchanged) * @param subsChanged true if subscriptions may have changed * (false if known unchanged) */ public void updateClient( Client client, boolean metaChanged, boolean subsChanged ) { synchronized ( clientMapView_ ) { clientMapView_.notifyAll(); } } /** * Returns an unmodifiable Map representing the client list. * Keys are client IDs and values are {@link org.astrogrid.samp.Client} * objects. *

This map is {@link java.util.Collections#synchronizedMap synchronized} * which means that to iterate over any of its views * you must synchronize on it. * When the map or any of its contents changes, it will receive a * {@link java.lang.Object#notifyAll}. * * @return id -> Client map */ public Map getClientMap() { return clientMapView_; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/client/package.html000066400000000000000000000004601356450004300247400ustar00rootroot00000000000000 Classes required only for SAMP clients.

Clients will normally use a {@link org.astrogrid.samp.client.HubConnector} to keep track of connections with a SAMP hub. However clients requiring a lower-level interface may simply use a {@link org.astrogrid.samp.client.HubConnection} object. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/000077500000000000000000000000001356450004300217655ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/AbstractCallActionManager.java000066400000000000000000000353171356450004300276310ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.awt.event.ActionEvent; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ListModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.LogResultHandler; import org.astrogrid.samp.client.ResponseHandler; import org.astrogrid.samp.client.ResultHandler; /** * Partial SendActionManager implementation which * uses the Asynchronous Call/Response delivery pattern. * It supplies most of the machinery required for tracking what happened * to responses to messages sent at the same time, but does not * implement the actual {@link #createBroadcastAction} method. * Subclasses are provided which do this. * * @author Mark Taylor * @since 11 Nov 2008 */ public abstract class AbstractCallActionManager extends SendActionManager { private final Component parent_; private final GuiHubConnector connector_; private final CallResponseHandler responder_; private static final Logger logger_ = Logger.getLogger( AbstractCallActionManager.class.getName() ); /** * Constructor. * * @param parent parent component * @param connector hub connector * @param clientListModel list model containing only those clients * which are suitable recipients; * all elements must be {@link Client}s */ public AbstractCallActionManager( Component parent, GuiHubConnector connector, ListModel clientListModel ) { super( connector, clientListModel ); parent_ = parent; connector_ = connector; responder_ = new CallResponseHandler(); connector_.addResponseHandler( responder_ ); connector_.addConnectionListener( responder_ ); updateState(); } /** * Must be implemented by concrete subclasses. */ abstract protected Action createBroadcastAction(); /** * Returns an object which will be informed of the results of a single- * or multiple-recipient send as they arrive. * This method will be called from the event dispatch thread. * *

The default implementation returns an instance of * {@link org.astrogrid.samp.client.LogResultHandler}. * * @param connection connection object * @param msg the message which was sent * @param recipients the recipients to whom the message was sent * @return result handler object */ protected ResultHandler createResultHandler( HubConnection connection, Message msg, Client[] recipients ) { return new LogResultHandler( msg ); } /** * Releases resources associated with this object. * Specifically, it removes listeners from the hub connector. * Following a call to this method, this object should not be used again. */ public void dispose() { connector_.removeResponseHandler( responder_ ); connector_.removeConnectionListener( responder_ ); } /** * Returns the Message object which is to be transmitted by this manager * to a given client. This is called by the action returned by * {@link #getSendAction}. * * @param client target * @return message */ protected abstract Map createMessage( Client client ) throws Exception; protected Action getSendAction( Client client ) { return new SendAction( client ); } /** * Creates and returns a new tag which will be attached to * an outgoing message, and updates internal structures so that * it will be recognised in the future. * A subsequent call to {@link #registerHandler} should be made for the * returned tag. * * @return new tag */ public String createTag() { return responder_.createTag(); } /** * Registers a result handler to handle results corresponding to a * message tag. * * @param tag tag returned by an earlier invocation of * {@link #createTag} * @param recipients clients from which responses are expected * @param handler result handler for responses; may be null * if no handling is required */ public void registerHandler( String tag, Client[] recipients, ResultHandler handler ) { responder_.registerHandler( tag, recipients, handler ); } /** * Action which performs a send to a particular client. */ private class SendAction extends AbstractAction { private final Client client_; private final String cName_; /** * Constructor. * * @param client target client */ SendAction( Client client ) { client_ = client; cName_ = client.toString(); putValue( NAME, cName_ ); putValue( SHORT_DESCRIPTION, "Transmit to " + cName_ + " using SAMP protocol" ); } public void actionPerformed( ActionEvent evt ) { boolean sent = false; Message msg = null; HubConnection connection = null; String tag = null; // Attempt to send the messsage. try { msg = Message.asMessage( createMessage( client_ ) ); msg.check(); connection = connector_.getConnection(); if ( connection != null ) { tag = responder_.createTag(); connection.call( client_.getId(), tag, msg ); sent = true; } } catch ( Exception e ) { ErrorDialog.showError( parent_, "Send Error", "Send failure " + e.getMessage(), e ); } // If it was sent, arrange for the result to be processed by // a suitable result handler. if ( sent ) { assert connection != null; assert msg != null; assert tag != null; Client[] recipients = new Client[] { client_ }; ResultHandler handler = createResultHandler( connection, msg, recipients ); responder_.registerHandler( tag, recipients, handler ); } } public boolean equals( Object o ) { if ( o instanceof SendAction ) { SendAction other = (SendAction) o; return this.client_.equals( other.client_ ) && this.cName_.equals( other.cName_ ); } else { return false; } } public int hashCode() { return client_.hashCode() * 23 + cName_.hashCode(); } } /** * ResponseHandler implementation for use by this class. * It handles all SAMP responses for calls which have been made by * this object and passes them on to the appropriate ResultHandlers. */ private class CallResponseHandler implements ResponseHandler, ChangeListener { private int iCall_; private final Map tagMap_; /** * Constructor. */ CallResponseHandler() { tagMap_ = Collections.synchronizedMap( new HashMap() ); } /** * Creates and returns a new tag which will be attached to * an outgoing message, and updates internal structures so that * it will be recognised in the future. * A subsequent call to {@link #registerHandler} should be made for the * returned tag. * * @return new tag */ public synchronized String createTag() { String tag = connector_.createTag( this ); tagMap_.put( tag, null ); return tag; } /** * Registers a result handler to handle results corresponding to a * message tag. * * @param tag tag returned by an earlier invocation of * {@link #createTag} * @param recipients clients from which responses are expected * @param handler result handler for responses; may be null * if no handling is required */ public void registerHandler( String tag, Client[] recipients, ResultHandler handler ) { synchronized ( tagMap_ ) { if ( handler != null ) { tagMap_.put( tag, new TagInfo( recipients, handler ) ); } else { tagMap_.remove( tag ); } tagMap_.notifyAll(); } } public boolean ownsTag( String tag ) { return tagMap_.containsKey( tag ); } public void receiveResponse( HubConnection connection, final String responderId, final String tag, final Response response ) { synchronized ( tagMap_ ) { if ( tagMap_.containsKey( tag ) ) { // If the result handler is already registered, pass the // result on to it. TagInfo info = (TagInfo) tagMap_.get( tag ); if ( info != null ) { processResponse( tag, info, responderId, response ); } // If the response was received very quickly, it's possible // that the handler has not been registered yet. // In this case, wait until it is. // Do this in a separate thread so that the // receiveResponse can return quickly (not essential, but // good behaviour). else { new Thread( "TagWaiter-" + tag ) { public void run() { TagInfo tinfo; try { synchronized ( tagMap_ ) { do { tinfo = (TagInfo) tagMap_.get( tag ); if ( tinfo == null ) { tagMap_.wait(); } } while ( tinfo == null ); } processResponse( tag, tinfo, responderId, response ); } catch ( InterruptedException e ) { logger_.warning( "Interrupted??" ); } } }.start(); } } // Shouldn't happen - HubConnector should not have invoked // in this case. else { logger_.warning( "Receive response for unknown tag " + tag + "??" ); return; } } } /** * Does the work of passing on a received response to a registered * result handler. * * @param tag message tag * @param info tag handling information object * @param responderId client ID of responder * @param response response object */ private void processResponse( String tag, TagInfo info, String responderId, Response response ) { ResultHandler handler = info.handler_; Map recipientMap = info.recipientMap_; synchronized ( info ) { Client responder = (Client) recipientMap.remove( responderId ); // Pass response on to handler. if ( responder != null ) { handler.result( responder, response ); } // If there are no more to come, notify the handler of this. if ( recipientMap.isEmpty() ) { handler.done(); } } // Unregister the handler if no more responses are expected for it. synchronized ( tagMap_ ) { if ( recipientMap.isEmpty() ) { tagMap_.remove( tag ); } } } public void stateChanged( ChangeEvent evt ) { if ( ! connector_.isConnected() ) { hubDisconnected(); } } /** * Called when the connection to the hub disappears. */ private void hubDisconnected() { synchronized ( tagMap_ ) { // Notify all result handlers that they will receive no more // responses, then unregister them all. for ( Iterator it = tagMap_.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); String tag = (String) entry.getKey(); TagInfo info = (TagInfo) entry.getValue(); if ( info != null ) { info.handler_.done(); } it.remove(); } } } } /** * Aggregates information required for handling responses which * correspond to a particular message tag. */ private static class TagInfo { final Map recipientMap_; final ResultHandler handler_; /** * Constructor. * * @param recipients recipients of message * @param handler handler for responses */ public TagInfo( Client[] recipients, ResultHandler handler ) { recipientMap_ = Collections.synchronizedMap( new HashMap() ); for ( int i = 0; i < recipients.length; i++ ) { Client recipient = recipients[ i ]; recipientMap_.put( recipient.getId(), recipient ); } handler_ = handler; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/BrowserLauncher.java000066400000000000000000000675241356450004300257530ustar00rootroot00000000000000// package edu.stanford.ejalbert; // // This class taken from http://browserlauncher.sourceforge.net/ // and renamed with a couple of tweaks for convenience. // Original version is BrowserLauncher 1.4b1. package org.astrogrid.samp.gui; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * BrowserLauncher is a class that provides one static method, openURL, which opens the default * web browser for the current user of the system to the given URL. It may support other * protocols depending on the system -- mailto, ftp, etc. -- but that has not been rigorously * tested and is not guaranteed to work. *

* Yes, this is platform-specific code, and yes, it may rely on classes on certain platforms * that are not part of the standard JDK. What we're trying to do, though, is to take something * that's frequently desirable but inherently platform-specific -- opening a default browser -- * and allow programmers (you, for example) to do so without worrying about dropping into native * code or doing anything else similarly evil. *

* Anyway, this code is completely in Java and will run on all JDK 1.1-compliant systems without * modification or a need for additional libraries. All classes that are required on certain * platforms to allow this to run are dynamically loaded at runtime via reflection and, if not * found, will not cause this to do anything other than returning an error when opening the * browser. *

* There are certain system requirements for this class, as it's running through Runtime.exec(), * which is Java's way of making a native system call. Currently, this requires that a Macintosh * have a Finder which supports the GURL event, which is true for Mac OS 8.0 and 8.1 systems that * have the Internet Scripting AppleScript dictionary installed in the Scripting Additions folder * in the Extensions folder (which is installed by default as far as I know under Mac OS 8.0 and * 8.1), and for all Mac OS 8.5 and later systems. On Windows, it only runs under Win32 systems * (Windows 95, 98, and NT 4.0, as well as later versions of all). On other systems, this drops * back from the inherently platform-sensitive concept of a default browser and simply attempts * to launch Netscape via a shell command. *

* This code is Copyright 1999-2001 by Eric Albert (ejalbert@cs.stanford.edu) and may be * redistributed or modified in any form without restrictions as long as the portion of this * comment from this paragraph through the end of the comment is not removed. The author * requests that he be notified of any application, applet, or other binary that makes use of * this code, but that's more out of curiosity than anything and is not required. This software * includes no warranty. The author is not repsonsible for any loss of data or functionality * or any adverse or unexpected effects of using this software. *

* Credits: *
Steven Spencer, JavaWorld magazine (Java Tip 66) *
Thanks also to Ron B. Yeh, Eric Shapiro, Ben Engber, Paul Teitlebaum, Andrea Cantatore, * Larry Barowski, Trevor Bedzek, Frank Miedrich, and Ron Rabakukk * * @author Eric Albert (ejalbert@cs.stanford.edu) * @version 1.4b1 (Released June 20, 2001) */ class BrowserLauncher { /** * The Java virtual machine that we are running on. Actually, in most cases we only care * about the operating system, but some operating systems require us to switch on the VM. */ private static int jvm; /** The browser for the system */ private static Object browser; /** * Caches whether any classes, methods, and fields that are not part of the JDK and need to * be dynamically loaded at runtime loaded successfully. *

* Note that if this is false, openURL() will always return an * IOException. */ private static boolean loadedWithoutErrors; /** The com.apple.mrj.MRJFileUtils class */ private static Class mrjFileUtilsClass; /** The com.apple.mrj.MRJOSType class */ private static Class mrjOSTypeClass; /** The com.apple.MacOS.AEDesc class */ private static Class aeDescClass; /** The (int) method of com.apple.MacOS.AETarget */ private static Constructor aeTargetConstructor; /** The (int, int, int) method of com.apple.MacOS.AppleEvent */ private static Constructor appleEventConstructor; /** The (String) method of com.apple.MacOS.AEDesc */ private static Constructor aeDescConstructor; /** The findFolder method of com.apple.mrj.MRJFileUtils */ private static Method findFolder; /** The getFileCreator method of com.apple.mrj.MRJFileUtils */ private static Method getFileCreator; /** The getFileType method of com.apple.mrj.MRJFileUtils */ private static Method getFileType; /** The openURL method of com.apple.mrj.MRJFileUtils */ private static Method openURL; /** The makeOSType method of com.apple.MacOS.OSUtils */ private static Method makeOSType; /** The putParameter method of com.apple.MacOS.AppleEvent */ private static Method putParameter; /** The sendNoReply method of com.apple.MacOS.AppleEvent */ private static Method sendNoReply; /** Actually an MRJOSType pointing to the System Folder on a Macintosh */ private static Object kSystemFolderType; /** The keyDirectObject AppleEvent parameter type */ private static Integer keyDirectObject; /** The kAutoGenerateReturnID AppleEvent code */ private static Integer kAutoGenerateReturnID; /** The kAnyTransactionID AppleEvent code */ private static Integer kAnyTransactionID; /** The linkage object required for JDirect 3 on Mac OS X. */ private static Object linkage; /** The framework to reference on Mac OS X */ private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox"; /** JVM constant for MRJ 2.0 */ private static final int MRJ_2_0 = 0; /** JVM constant for MRJ 2.1 or later */ private static final int MRJ_2_1 = 1; /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */ private static final int MRJ_3_0 = 3; /** JVM constant for MRJ 3.1 */ private static final int MRJ_3_1 = 4; /** JVM constant for any Windows NT JVM */ private static final int WINDOWS_NT = 5; /** JVM constant for any Windows 9x JVM */ private static final int WINDOWS_9x = 6; /** JVM constant for any other platform */ private static final int OTHER = -1; /** * The file type of the Finder on a Macintosh. Hardcoding "Finder" would keep non-U.S. English * systems from working properly. */ private static final String FINDER_TYPE = "FNDR"; /** * The creator code of the Finder on a Macintosh, which is needed to send AppleEvents to the * application. */ private static final String FINDER_CREATOR = "MACS"; /** The name for the AppleEvent type corresponding to a GetURL event. */ private static final String GURL_EVENT = "GURL"; /** * The first parameter that needs to be passed into Runtime.exec() to open the default web * browser on Windows. */ private static final String FIRST_WINDOWS_PARAMETER = "/c"; /** The second parameter for Runtime.exec() on Windows. */ private static final String SECOND_WINDOWS_PARAMETER = "start"; /** * The third parameter for Runtime.exec() on Windows. This is a "title" * parameter that the command line expects. Setting this parameter allows * URLs containing spaces to work. */ private static final String THIRD_WINDOWS_PARAMETER = "\"\""; /** * The shell parameters for Netscape that opens a given URL in an already-open copy of Netscape * on many command-line systems. */ private static final String NETSCAPE_REMOTE_PARAMETER = "-remote"; private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL("; private static final String NETSCAPE_OPEN_PARAMETER_END = ")'"; /** * The message from any exception thrown throughout the initialization process. */ private static String errorMessage; /** * An initialization block that determines the operating system and loads the necessary * runtime data. */ static { loadedWithoutErrors = true; String osName = java.lang.System.getProperty("os.name"); if (osName.startsWith("Mac OS")) { String mrjVersion = java.lang.System.getProperty("mrj.version"); String majorMRJVersion = mrjVersion.substring(0, 3); try { double version = Double.valueOf(majorMRJVersion).doubleValue(); if (version == 2) { jvm = MRJ_2_0; } else if (version >= 2.1 && version < 3) { // Assume that all 2.x versions of MRJ work the same. MRJ 2.1 actually // works via Runtime.exec() and 2.2 supports that but has an openURL() method // as well that we currently ignore. jvm = MRJ_2_1; } else if (version == 3.0) { jvm = MRJ_3_0; } else if (version >= 3.1) { // Assume that all 3.1 and later versions of MRJ work the same. jvm = MRJ_3_1; } else { loadedWithoutErrors = false; errorMessage = "Unsupported MRJ version: " + version; } } catch (NumberFormatException nfe) { loadedWithoutErrors = false; errorMessage = "Invalid MRJ version: " + mrjVersion; } } else if (osName.startsWith("Windows")) { if (osName.indexOf("9") != -1) { jvm = WINDOWS_9x; } else { jvm = WINDOWS_NT; } } else { jvm = OTHER; } if (loadedWithoutErrors) { // if we haven't hit any errors yet loadedWithoutErrors = loadClasses(); } } /** * This class should be never be instantiated; this just ensures so. */ private BrowserLauncher() { } /** * Called by a static initializer to load any classes, fields, and methods required at runtime * to locate the user's web browser. * @return true if all intialization succeeded * false if any portion of the initialization failed */ private static boolean loadClasses() { switch (jvm) { case MRJ_2_0: try { Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget"); Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils"); Class appleEventClass = Class.forName("com.apple.MacOS.AppleEvent"); Class aeClass = Class.forName("com.apple.MacOS.ae"); aeDescClass = Class.forName("com.apple.MacOS.AEDesc"); aeTargetConstructor = aeTargetClass.getDeclaredConstructor(new Class [] { int.class }); appleEventConstructor = appleEventClass.getDeclaredConstructor(new Class[] { int.class, int.class, aeTargetClass, int.class, int.class }); aeDescConstructor = aeDescClass.getDeclaredConstructor(new Class[] { String.class }); makeOSType = osUtilsClass.getDeclaredMethod("makeOSType", new Class [] { String.class }); putParameter = appleEventClass.getDeclaredMethod("putParameter", new Class[] { int.class, aeDescClass }); sendNoReply = appleEventClass.getDeclaredMethod("sendNoReply", new Class[] { }); Field keyDirectObjectField = aeClass.getDeclaredField("keyDirectObject"); keyDirectObject = (Integer) keyDirectObjectField.get(null); Field autoGenerateReturnIDField = appleEventClass.getDeclaredField("kAutoGenerateReturnID"); kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField.get(null); Field anyTransactionIDField = appleEventClass.getDeclaredField("kAnyTransactionID"); kAnyTransactionID = (Integer) anyTransactionIDField.get(null); } catch (ClassNotFoundException cnfe) { errorMessage = cnfe.getMessage(); return false; } catch (NoSuchMethodException nsme) { errorMessage = nsme.getMessage(); return false; } catch (NoSuchFieldException nsfe) { errorMessage = nsfe.getMessage(); return false; } catch (IllegalAccessException iae) { errorMessage = iae.getMessage(); return false; } break; case MRJ_2_1: try { mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType"); Field systemFolderField = mrjFileUtilsClass.getDeclaredField("kSystemFolderType"); kSystemFolderType = systemFolderField.get(null); findFolder = mrjFileUtilsClass.getDeclaredMethod("findFolder", new Class[] { mrjOSTypeClass }); getFileCreator = mrjFileUtilsClass.getDeclaredMethod("getFileCreator", new Class[] { File.class }); getFileType = mrjFileUtilsClass.getDeclaredMethod("getFileType", new Class[] { File.class }); } catch (ClassNotFoundException cnfe) { errorMessage = cnfe.getMessage(); return false; } catch (NoSuchFieldException nsfe) { errorMessage = nsfe.getMessage(); return false; } catch (NoSuchMethodException nsme) { errorMessage = nsme.getMessage(); return false; } catch (SecurityException se) { errorMessage = se.getMessage(); return false; } catch (IllegalAccessException iae) { errorMessage = iae.getMessage(); return false; } break; case MRJ_3_0: try { Class linker = Class.forName("com.apple.mrj.jdirect.Linker"); Constructor constructor = linker.getConstructor(new Class[]{ Class.class }); linkage = constructor.newInstance(new Object[] { BrowserLauncher.class }); } catch (ClassNotFoundException cnfe) { errorMessage = cnfe.getMessage(); return false; } catch (NoSuchMethodException nsme) { errorMessage = nsme.getMessage(); return false; } catch (InvocationTargetException ite) { errorMessage = ite.getMessage(); return false; } catch (InstantiationException ie) { errorMessage = ie.getMessage(); return false; } catch (IllegalAccessException iae) { errorMessage = iae.getMessage(); return false; } break; case MRJ_3_1: try { mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils"); openURL = mrjFileUtilsClass.getDeclaredMethod("openURL", new Class[] { String.class }); } catch (ClassNotFoundException cnfe) { errorMessage = cnfe.getMessage(); return false; } catch (NoSuchMethodException nsme) { errorMessage = nsme.getMessage(); return false; } break; default: break; } return true; } /** * Attempts to locate the default web browser on the local system. Caches results so it * only locates the browser once for each use of this class per JVM instance. * @return The browser for the system. Note that this may not be what you would consider * to be a standard web browser; instead, it's the application that gets called to * open the default web browser. In some cases, this will be a non-String object * that provides the means of calling the default browser. */ private static Object locateBrowser() { if (browser != null) { return browser; } switch (jvm) { case MRJ_2_0: try { Integer finderCreatorCode = (Integer) makeOSType.invoke(null, new Object[] { FINDER_CREATOR }); Object aeTarget = aeTargetConstructor.newInstance(new Object[] { finderCreatorCode }); Integer gurlType = (Integer) makeOSType.invoke(null, new Object[] { GURL_EVENT }); Object appleEvent = appleEventConstructor.newInstance(new Object[] { gurlType, gurlType, aeTarget, kAutoGenerateReturnID, kAnyTransactionID }); // Don't set browser = appleEvent because then the next time we call // locateBrowser(), we'll get the same AppleEvent, to which we'll already have // added the relevant parameter. Instead, regenerate the AppleEvent every time. // There's probably a way to do this better; if any has any ideas, please let // me know. return appleEvent; } catch (IllegalAccessException iae) { browser = null; errorMessage = iae.getMessage(); return browser; } catch (InstantiationException ie) { browser = null; errorMessage = ie.getMessage(); return browser; } catch (InvocationTargetException ite) { browser = null; errorMessage = ite.getMessage(); return browser; } case MRJ_2_1: File systemFolder; try { systemFolder = (File) findFolder.invoke(null, new Object[] { kSystemFolderType }); } catch (IllegalArgumentException iare) { browser = null; errorMessage = iare.getMessage(); return browser; } catch (IllegalAccessException iae) { browser = null; errorMessage = iae.getMessage(); return browser; } catch (InvocationTargetException ite) { browser = null; errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); return browser; } String[] systemFolderFiles = systemFolder.list(); // Avoid a FilenameFilter because that can't be stopped mid-list for(int i = 0; i < systemFolderFiles.length; i++) { try { File file = new File(systemFolder, systemFolderFiles[i]); if (!file.isFile()) { continue; } // We're looking for a file with a creator code of 'MACS' and // a type of 'FNDR'. Only requiring the type results in non-Finder // applications being picked up on certain Mac OS 9 systems, // especially German ones, and sending a GURL event to those // applications results in a logout under Multiple Users. Object fileType = getFileType.invoke(null, new Object[] { file }); if (FINDER_TYPE.equals(fileType.toString())) { Object fileCreator = getFileCreator.invoke(null, new Object[] { file }); if (FINDER_CREATOR.equals(fileCreator.toString())) { browser = file.toString(); // Actually the Finder, but that's OK return browser; } } } catch (IllegalArgumentException iare) { browser = browser; errorMessage = iare.getMessage(); return null; } catch (IllegalAccessException iae) { browser = null; errorMessage = iae.getMessage(); return browser; } catch (InvocationTargetException ite) { browser = null; errorMessage = ite.getTargetException().getClass() + ": " + ite.getTargetException().getMessage(); return browser; } } browser = null; break; case MRJ_3_0: case MRJ_3_1: browser = ""; // Return something non-null break; case WINDOWS_NT: browser = "cmd.exe"; break; case WINDOWS_9x: browser = "command.com"; break; case OTHER: default: browser = "firefox"; break; } return browser; } /** * Attempts to open the default web browser to the given URL. * @param url The URL to open * @throws IOException If the web browser could not be located or does not run */ public static void openURL(String url) throws IOException { if (!loadedWithoutErrors) { throw new IOException("Exception in finding browser: " + errorMessage); } Object browser = locateBrowser(); if (browser == null) { throw new IOException("Unable to locate browser: " + errorMessage); } switch (jvm) { case MRJ_2_0: Object aeDesc = null; try { aeDesc = aeDescConstructor.newInstance(new Object[] { url }); putParameter.invoke(browser, new Object[] { keyDirectObject, aeDesc }); sendNoReply.invoke(browser, new Object[] { }); } catch (InvocationTargetException ite) { throw new IOException("InvocationTargetException while creating AEDesc: " + ite.getMessage()); } catch (IllegalAccessException iae) { throw new IOException("IllegalAccessException while building AppleEvent: " + iae.getMessage()); } catch (InstantiationException ie) { throw new IOException("InstantiationException while creating AEDesc: " + ie.getMessage()); } finally { aeDesc = null; // Encourage it to get disposed if it was created browser = null; // Ditto } break; case MRJ_2_1: Runtime.getRuntime().exec(new String[] { (String) browser, url } ); break; case MRJ_3_0: int[] instance = new int[1]; int result = ICStart(instance, 0); if (result == 0) { int[] selectionStart = new int[] { 0 }; byte[] urlBytes = url.getBytes(); int[] selectionEnd = new int[] { urlBytes.length }; result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes, urlBytes.length, selectionStart, selectionEnd); if (result == 0) { // Ignore the return value; the URL was launched successfully // regardless of what happens here. ICStop(instance); } else { throw new IOException("Unable to launch URL: " + result); } } else { throw new IOException("Unable to create an Internet Config instance: " + result); } break; case MRJ_3_1: try { openURL.invoke(null, new Object[] { url }); } catch (InvocationTargetException ite) { throw new IOException("InvocationTargetException while calling openURL: " + ite.getMessage()); } catch (IllegalAccessException iae) { throw new IOException("IllegalAccessException while calling openURL: " + iae.getMessage()); } break; case WINDOWS_NT: case WINDOWS_9x: // Add quotes around the URL to allow ampersands and other special // characters to work. Process process = Runtime.getRuntime().exec(new String[] { (String) browser, FIRST_WINDOWS_PARAMETER, SECOND_WINDOWS_PARAMETER, THIRD_WINDOWS_PARAMETER, '"' + url + '"' }); // This avoids a memory leak on some versions of Java on Windows. // That's hinted at in . try { process.waitFor(); process.exitValue(); } catch (InterruptedException ie) { throw new IOException("InterruptedException while launching browser: " + ie.getMessage()); } break; case OTHER: // Assume that we're on Unix and that Netscape is installed // First, attempt to open the URL in a currently running session of Netscape process = Runtime.getRuntime().exec(new String[] { (String) browser, NETSCAPE_REMOTE_PARAMETER, NETSCAPE_OPEN_PARAMETER_START + url + NETSCAPE_OPEN_PARAMETER_END }); try { int exitCode = process.waitFor(); if (exitCode != 0) { // if Netscape was not open Runtime.getRuntime().exec(new String[] { (String) browser, url }); } } catch (InterruptedException ie) { throw new IOException("InterruptedException while launching browser: " + ie.getMessage()); } break; default: // This should never occur, but if it does, we'll try the simplest thing possible Runtime.getRuntime().exec(new String[] { (String) browser, url }); break; } } /** * Methods required for Mac OS X. The presence of native methods does not cause * any problems on other platforms. */ private native static int ICStart(int[] instance, int signature); private native static int ICStop(int[] instance); private native static int ICLaunchURL(int instance, byte[] hint, byte[] data, int len, int[] selectionStart, int[] selectionEnd); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/ClientListCellRenderer.java000066400000000000000000000112441356450004300271730ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.awt.Graphics; import java.awt.Font; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.util.HashMap; import java.util.Map; import javax.swing.DefaultListCellRenderer; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.JList; import org.astrogrid.samp.Client; import org.astrogrid.samp.Metadata; /** * List Cell Renderer for use with {@link org.astrogrid.samp.Client} objects. * * @author Mark Taylor * @since 16 Jul 2008 */ class ClientListCellRenderer extends DefaultListCellRenderer { private Font[] labelFonts_; private IconStore iconStore_; private final Map addHints_; /** * Constructor. */ public ClientListCellRenderer() { iconStore_ = new IconStore( IconStore.createEmptyIcon( 16 ) ); addHints_ = new HashMap(); addHints_.put( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY ); addHints_.put( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC ); } /** * Attempts to return a human-readable text label for the given client. * * @param client to find label for * @return human-readable label for client if available; if nothing * better than the public ID can be found, null is returned */ protected String getLabel( Client client ) { Metadata meta = client.getMetadata(); return meta != null ? meta.getName() : null; } protected void paintComponent( Graphics g ) { // Improve the rendering as much as possible, since we are typically // rendering some very small graphics that can look ugly if // the resampling puts pixels out of place. Graphics2D g2 = (Graphics2D) g; RenderingHints oldHints = g2.getRenderingHints(); g2.addRenderingHints( addHints_ ); super.paintComponent( g ); g2.setRenderingHints( oldHints ); } public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSel, boolean hasFocus ) { Component c = super.getListCellRendererComponent( list, value, index, isSel, hasFocus ); if ( c instanceof JLabel && value instanceof Client ) { JLabel jl = (JLabel) c; Client client = (Client) value; String label = getLabel( client ); String text = label == null ? client.getId() : label; Font font = getLabelFont( label == null ); int size; try { size = (int) Math.ceil( font.getMaxCharBounds( ((Graphics2D) list.getGraphics()) .getFontRenderContext() ) .getHeight() ); } catch ( NullPointerException e ) { size = 16; } jl.setText( text ); jl.setFont( font ); jl.setIcon( reshapeIcon( iconStore_.getIcon( client ), size ) ); } return c; } /** * Returns the font used by this label, or a variant. * * @param special true if the font is to look a bit different * @return font */ private Font getLabelFont( boolean special ) { if ( labelFonts_ == null ) { Font normalFont = getFont().deriveFont( Font.BOLD ); Font aliasFont = getFont().deriveFont( Font.PLAIN ); labelFonts_ = new Font[] { normalFont, aliasFont }; } return labelFonts_[ special ? 1 : 0 ]; } /** * Modifies an icon so that it has a fixed shape and positioning. * * @param icon input icon * @param height fixed icon height * @return reshaped icon */ static Icon reshapeIcon( Icon icon, final int height ) { double aspect = 2.0; final int width = (int) Math.ceil( aspect * height ); final Icon sIcon = IconStore.scaleIcon( icon, height, aspect, true ); final int xoff = ( width - sIcon.getIconWidth() ) / 2; return new Icon() { public int getIconWidth() { return width; } public int getIconHeight() { return height; } public void paintIcon( Component c, Graphics g, int x, int y ) { sIcon.paintIcon( c, g, x + xoff, y ); } }; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/ClientPanel.java000066400000000000000000000261651356450004300250400ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.AbstractListModel; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.ListModel; import javax.swing.border.Border; import org.astrogrid.samp.Client; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.hub.HubClient; /** * Component which displays details about a {@link org.astrogrid.samp.Client}. * * @author Mark Taylor * @since 16 Jul 2008 */ public class ClientPanel extends JPanel { private final JTextField idField_; private final JTextField profileField_; private final Box metaBox_; private final JList subsList_; private Client client_; private static final int INFO_WIDTH = 240; private final Logger logger_ = Logger.getLogger( ClientPanel.class.getName() ); /** * Constructor. * * @param hubLike true if this will be displaying clients implementing * the HubClient interface */ public ClientPanel( boolean hubLike ) { super( new BorderLayout() ); JSplitPane splitter = new JSplitPane( JSplitPane.VERTICAL_SPLIT ); splitter.setBorder( BorderFactory.createEmptyBorder() ); splitter.setOneTouchExpandable( true ); splitter.setResizeWeight( 0.5 ); add( splitter, BorderLayout.CENTER ); // Construct and place registration subpanel. Box regBox = Box.createVerticalBox(); regBox.setBorder( createTitledBorder( "Registration" ) ); Box idLine = Box.createHorizontalBox(); idField_ = new JTextField(); idField_.setEditable( false ); idField_.setBorder( BorderFactory.createEmptyBorder() ); idLine.add( new JLabel( "Public ID: " ) ); idLine.add( idField_ ); regBox.add( idLine ); if ( hubLike ) { profileField_ = new JTextField(); profileField_.setEditable( false ); profileField_.setBorder( BorderFactory.createEmptyBorder() ); Box profileLine = Box.createHorizontalBox(); profileLine.add( new JLabel( "Profile: " ) ); profileLine.add( profileField_ ); regBox.add( profileLine ); } else { profileField_ = null; } add( regBox, BorderLayout.NORTH ); // Construct and place metadata subpanel. metaBox_ = Box.createVerticalBox(); JPanel metaPanel = new JPanel( new BorderLayout() ); metaPanel.add( metaBox_, BorderLayout.NORTH ); JScrollPane metaScroller = new JScrollPane( metaPanel ); metaScroller.setBorder( createTitledBorder( "Metadata" ) ); metaScroller.setPreferredSize( new Dimension( INFO_WIDTH, 120 ) ); splitter.setTopComponent( metaScroller ); // Construct and place subscriptions subpanel. Box subsBox = Box.createVerticalBox(); subsList_ = new JList(); JScrollPane subsScroller = new JScrollPane( subsList_ ); subsScroller.setBorder( createTitledBorder( "Subscriptions" ) ); subsScroller.setPreferredSize( new Dimension( INFO_WIDTH, 120 ) ); subsBox.add( subsScroller ); splitter.setBottomComponent( subsBox ); } /** * Updates this component to display the current state of a given client. * * @param client client, or null to clear display */ public void setClient( Client client ) { idField_.setText( client == null ? null : client.getId() ); if ( profileField_ != null ) { profileField_.setText( client instanceof HubClient ? ((HubClient) client).getProfileToken() .getProfileName() : null ); } setMetadata( client == null ? null : client.getMetadata() ); setSubscriptions( client == null ? null : client.getSubscriptions() ); client_ = client; } /** * Returns the most recently displayed client. * * @return client */ public Client getClient() { return client_; } /** * Updates this component's metadata panel to display the current state * of a given metadata object. * * @param meta metadata map, or null to clear metadata display */ public void setMetadata( Metadata meta ) { metaBox_.removeAll(); if ( meta != null ) { for ( Iterator it = meta.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); String key = (String) entry.getKey(); Object value = entry.getValue(); Box keyBox = Box.createHorizontalBox(); keyBox.add( new JLabel( key + ":" ) ); keyBox.add( Box.createHorizontalGlue() ); metaBox_.add( keyBox ); Box valueBox = Box.createHorizontalBox(); valueBox.add( Box.createHorizontalStrut( 24 ) ); valueBox.add( createViewer( value ) ); metaBox_.add( valueBox ); } } metaBox_.add( Box.createVerticalGlue() ); metaBox_.repaint(); metaBox_.revalidate(); } /** * Updates this component's subscriptions panel to display the current * state of a given subscriptions object. * * @param subs subscriptions map, or null to clear subscriptions display */ public void setSubscriptions( Subscriptions subs ) { final Object[] subscriptions = subs == null ? new Object[ 0 ] : subs.keySet().toArray(); subsList_.setModel( new AbstractListModel() { public int getSize() { return subscriptions.length; } public Object getElementAt( int index ) { return subscriptions[ index ]; } } ); } /** * Attempts to open a URL in some kind of external browser. * * @param url URL to view */ public void openURL( URL url ) throws IOException { BrowserLauncher.openURL( url.toString() ); } /** * Returns a graphical component which displays a legal SAMP object * (SAMP map, list or string). * * @param value SAMP object * @return new component displaying value */ private JComponent createViewer( Object value ) { if ( value instanceof String ) { JTextField field = new JTextField(); field.setEditable( false ); field.setText( (String) value ); field.setCaretPosition( 0 ); try { final URL url = new URL( (String) value ); field.setForeground( Color.BLUE ); field.addMouseListener( new MouseAdapter() { public void mouseClicked( MouseEvent evt ) { try { openURL( url ); } catch ( IOException e ) { Toolkit.getDefaultToolkit().beep(); logger_.warning( "Can't open URL " + url + e ); } } } ); } catch ( MalformedURLException e ) { // not a URL - fine } return field; } else if ( value instanceof List ) { return new JList( ((List) value).toArray() ); } else if ( value instanceof Map ) { JEditorPane edPane = new JEditorPane( "text/html", toHtml( value ) ); edPane.setEditable( false ); edPane.setCaretPosition( 0 ); return edPane; } else { return new JLabel( "???" ); } } /** * Returns an HTML representation of a legal SAMP object * (SAMP map, list or string). * * @param data SAMP object * @return HTML representation of data */ private static String toHtml( Object data ) { StringBuffer sbuf = new StringBuffer(); if ( data instanceof Map ) { sbuf.append( "

\n" ); for ( Iterator it = ((Map) data).entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); sbuf.append( "
" ) .append( htmlEscape( String.valueOf( entry.getKey() ) ) ) .append( "
\n" ) .append( "
" ) .append( toHtml( entry.getValue() ) ) .append( "
\n" ); } sbuf.append( "
\n" ); } else if ( data instanceof List ) { sbuf.append( "
    \n" ); for ( Iterator it = ((List) data).iterator(); it.hasNext(); ) { sbuf.append( "
  • " ) .append( toHtml( it.next() ) ) .append( "
  • \n" ); } sbuf.append( "
\n" ); } else if ( data instanceof String ) { sbuf.append( htmlEscape( (String) data ) ); } else { sbuf.append( "???" ); } return sbuf.toString(); } /** * Escapes a literal string for use within HTML text. * * @param text literal string * @return escaped version of text safe for use within HTML */ private static String htmlEscape( String text ) { int leng = text.length(); StringBuffer sbuf = new StringBuffer( leng ); for ( int i = 0; i < leng; i++ ) { char c = text.charAt( i ); switch ( c ) { case '<': sbuf.append( "<" ); break; case '>': sbuf.append( ">" ); break; case '&': sbuf.append( "&" ); break; default: sbuf.append( c ); } } return sbuf.toString(); } /** * Creates a titled border with a uniform style. * * @param title title text * @return border */ static Border createTitledBorder( String title ) { return BorderFactory.createCompoundBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ), BorderFactory.createTitledBorder( BorderFactory.createLineBorder( Color.BLACK ), title ) ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/ClientTransmissionHolder.java000066400000000000000000000013231356450004300276150ustar00rootroot00000000000000package org.astrogrid.samp.gui; import javax.swing.ListModel; import org.astrogrid.samp.Client; /** * Provides the means to obtain list models containing pending sent and * received transmissions. * * @author Mark Taylor * @since 26 Nov 2008 */ interface ClientTransmissionHolder { /** * Returns a list model containing messages sent by a given client. * * @return list model containing {@link Transmission} objects */ ListModel getTxListModel( Client client ); /** * Returns a list model containing messages received by a given client. * * @return list model containing {@link Transmission} objects */ ListModel getRxListModel( Client client ); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/ErrorDialog.java000066400000000000000000000110501356450004300250360ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.Frame; import java.awt.event.ActionEvent; import java.io.StringWriter; import java.io.PrintWriter; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; /** * Dialog window which displays an error message, possibly with some * verbose details optionally visible. * * @author Mark Taylor * @since 5 Sep 2008 */ public abstract class ErrorDialog extends JDialog { /** * Constructor. * * @param owner parent frame * @param title dialog title * @param summary short text string describing what's up */ protected ErrorDialog( Frame owner, String title, String summary ) { super( owner, title == null ? "Error" : title, true ); setDefaultCloseOperation( DISPOSE_ON_CLOSE ); // Define buttons. final JPanel main = new JPanel( new BorderLayout() ); JButton disposeButton = new JButton(); JButton detailButton = new JButton(); final JComponent dataBox = new JPanel( new BorderLayout() ); dataBox.add( new JLabel( summary ) ); JComponent buttonBox = Box.createHorizontalBox(); // Populate main panel. Border gapBorder = BorderFactory.createEmptyBorder( 5, 5, 5, 5 ); JComponent iconLabel = new JLabel( UIManager.getIcon( "OptionPane.errorIcon" ) ); iconLabel.setBorder( gapBorder ); main.add( iconLabel, BorderLayout.WEST ); buttonBox.add( Box.createHorizontalGlue() ); buttonBox.add( disposeButton ); buttonBox.add( Box.createHorizontalStrut( 10 ) ); buttonBox.add( detailButton ); buttonBox.add( Box.createHorizontalGlue() ); dataBox.setBorder( gapBorder ); main.add( buttonBox, BorderLayout.SOUTH ); main.add( dataBox, BorderLayout.CENTER ); main.setBorder( gapBorder ); // Set button action for dismiss button. disposeButton.setAction( new AbstractAction( "OK" ) { public void actionPerformed( ActionEvent evt ) { dispose(); } } ); // Set button action for display detail button. detailButton.setAction( new AbstractAction( "Show Details" ) { public void actionPerformed( ActionEvent evt ) { JTextArea ta = new JTextArea(); ta.setLineWrap( false ); ta.setEditable( false ); ta.append( getDetailText() ); ta.setCaretPosition( 0 ); JScrollPane scroller = new JScrollPane( ta ); dataBox.removeAll(); dataBox.add( scroller ); Dimension size = dataBox.getPreferredSize(); size.height = Math.min( size.height, 300 ); size.width = Math.min( size.width, 500 ); dataBox.revalidate(); dataBox.setPreferredSize( size ); pack(); setEnabled( false ); } } ); getContentPane().add( main ); } /** * Supplies the text to be displayed in the detail panel. * * @return detail text */ protected abstract String getDetailText(); /** * Pops up a window which shows the content of a exception. * * @param parent parent component * @param title window title * @param summary short text string * @param error throwable */ public static void showError( Component parent, String title, String summary, final Throwable error ) { Frame fparent = parent == null ? null : (Frame) SwingUtilities .getAncestorOfClass( Frame.class, parent ); JDialog dialog = new ErrorDialog( fparent, title, summary ) { protected String getDetailText() { StringWriter traceWriter = new StringWriter(); error.printStackTrace( new PrintWriter( traceWriter ) ); return traceWriter.toString(); } }; dialog.setLocationRelativeTo( parent ); dialog.pack(); dialog.setVisible( true ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/GuiClientSet.java000066400000000000000000000117161356450004300251750ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import javax.swing.ListModel; import javax.swing.SwingUtilities; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import org.astrogrid.samp.hub.BasicClientSet; import org.astrogrid.samp.hub.HubClient; import org.astrogrid.samp.hub.MessageRestriction; import org.astrogrid.samp.hub.ProfileToken; /** * ClientSet implementation used by GuiHubService. * It also implements {@link javax.swing.ListModel}. * * @author Mark Taylor * @since 20 Nov 2008 */ class GuiClientSet extends BasicClientSet implements ListModel { private final List clientList_; private final List listenerList_; private final static HubClient MORIBUND_CLIENT = new HubClient( "", new ProfileToken() { public String getProfileName() { return ""; } public MessageRestriction getMessageRestriction() { return null; } } ); /** * Constructor. * * @param clientIdComparator comparator for client IDs */ public GuiClientSet( Comparator clientIdComparator ) { super( clientIdComparator ); clientList_ = Collections.synchronizedList( new ArrayList() ); listenerList_ = new ArrayList(); } public synchronized void add( HubClient client ) { super.add( client ); int index = clientList_.size(); clientList_.add( client ); scheduleListDataEvent( ListDataEvent.INTERVAL_ADDED, index, index ); } public synchronized void remove( HubClient client ) { super.remove( client ); clientList_.remove( client ); int index = clientList_.size(); scheduleListDataEvent( ListDataEvent.INTERVAL_REMOVED, index, index ); } public synchronized HubClient[] getClients() { return (HubClient[]) clientList_.toArray( new HubClient[ 0 ] ); } public Object getElementAt( int index ) { try { return clientList_.get( index ); } // May be called from other than the event dispatch thread. catch ( IndexOutOfBoundsException e ) { return MORIBUND_CLIENT; } } public int getSize() { return clientList_.size(); } public void addListDataListener( ListDataListener l ) { listenerList_.add( l ); } public void removeListDataListener( ListDataListener l ) { listenerList_.remove( l ); } /** * Schedules notification of list data listeners that the attributes * of a client have changed. * May be called from any thread. * * @param client client which has changed */ public synchronized void scheduleClientChanged( HubClient client ) { for ( int ix = 0; ix < clientList_.size(); ix++ ) { if ( clientList_.get( ix ).equals( client ) ) { scheduleListDataEvent( ListDataEvent.CONTENTS_CHANGED, ix, ix ); return; } } scheduleListDataEvent( ListDataEvent.CONTENTS_CHANGED, 0, clientList_.size() ); } /** * Schedules notification of list data listeners about an event. * May be called from any thread. * * @param type ListDataEvent event type * @param int index0 ListDataEvent start index * @param int index1 ListDataEvent end index */ private void scheduleListDataEvent( int type, int index0, int index1 ) { if ( ! listenerList_.isEmpty() ) { final ListDataEvent evt = new ListDataEvent( this, type, index0, index1 ); SwingUtilities.invokeLater( new Runnable() { public void run() { fireListDataEvent( evt ); } } ); } } /** * Passes a ListDataEvent to all listeners. * Must be called from AWT event dispatch thread. * * @param evt event to forward */ public void fireListDataEvent( ListDataEvent evt ) { assert SwingUtilities.isEventDispatchThread(); int type = evt.getType(); for ( Iterator it = listenerList_.iterator(); it.hasNext(); ) { ListDataListener listener = (ListDataListener) it.next(); if ( type == ListDataEvent.INTERVAL_ADDED ) { listener.intervalAdded( evt ); } else if ( type == ListDataEvent.INTERVAL_REMOVED ) { listener.intervalRemoved( evt ); } else if ( type == ListDataEvent.CONTENTS_CHANGED ) { listener.contentsChanged( evt ); } else { assert false; } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/GuiHubConnector.java000066400000000000000000000643071356450004300257000ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JTextField; import javax.swing.ListCellRenderer; import javax.swing.ListModel; import javax.swing.SwingUtilities; import javax.swing.border.Border; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.HubConnector; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.client.TrackedClientSet; import org.astrogrid.samp.hub.Hub; import org.astrogrid.samp.hub.HubServiceMode; import org.astrogrid.samp.xmlrpc.XmlRpcKit; /** * Extends HubConnector to provide additional graphical functionality. * In particular Swing {@link javax.swing.Action}s are provided for * hub connection/disconnection * and the client list is made available as a {@link javax.swing.ListModel}. * See the {@link org.astrogrid.samp.client.HubConnector superclass} * documentation for details of how to use this class. * A number of utility methods build on these features to provide * Swing components and Actions which can be used directly to populate * application menus etc. * * @author Mark Taylor * @since 25 Nov 2008 */ public class GuiHubConnector extends HubConnector { private final ListModel clientListModel_; private final List connectionListenerList_; private final Map updateMap_; private boolean wasConnected_; static ConnectionUpdate ENABLE_ACTION = new ConnectionUpdate() { public void setConnected( Object action, boolean isConnected ) { ((Action) action).setEnabled( isConnected ); } }; static ConnectionUpdate DISABLE_ACTION = new ConnectionUpdate() { public void setConnected( Object action, boolean isConnected ) { ((Action) action).setEnabled( ! isConnected ); } }; static ConnectionUpdate REPAINT_COMPONENT = new ConnectionUpdate() { public void setConnected( Object comp, boolean isConnected ) { ((Component) comp).repaint(); } }; static ConnectionUpdate ENABLE_COMPONENT = new ConnectionUpdate() { public void setConnected( Object comp, boolean isConnected ) { ((Component) comp).setEnabled( isConnected ); } }; /** * Constructs a hub connector based on a given profile instance. * * @param profile profile implementation */ public GuiHubConnector( ClientProfile profile ) { super( profile, new ListModelTrackedClientSet() ); clientListModel_ = (ListModelTrackedClientSet) getClientSet(); connectionListenerList_ = new ArrayList(); updateMap_ = new WeakHashMap(); // Update state when hub connection starts/stops. addConnectionListener( new ChangeListener() { public void stateChanged( ChangeEvent evt ) { updateConnectionState(); } } ); updateConnectionState(); } protected void connectionChanged( boolean isConnected ) { super.connectionChanged( isConnected ); SwingUtilities.invokeLater( new Runnable() { public void run() { ChangeEvent evt = new ChangeEvent( GuiHubConnector.this ); for ( Iterator it = connectionListenerList_.iterator(); it.hasNext(); ) { ((ChangeListener) it.next()).stateChanged( evt ); } } } ); } /** * Adds a listener which will be notified when this connector * registers or unregisters with a hub. * * @param listener listener to add */ public void addConnectionListener( ChangeListener listener ) { connectionListenerList_.add( listener ); } /** * Removes a listener previously added by * {@link #addConnectionListener addConnectionListener}. * * @param listener listener to remove */ public void removeConnectionListener( ChangeListener listener ) { connectionListenerList_.remove( listener ); } /** * Returns a ListModel containing the registered clients. * Listeners to this model are correctly notified whenever any change * in its contents takes place. * * @return list model containing {@link Client} objects */ public ListModel getClientListModel() { return clientListModel_; } /** * Returns a list cell renderer suitable for use with the * client list model returned by {@link #getClientListModel}. * * @return list cell renderer for Client objects */ public ListCellRenderer createClientListCellRenderer() { return new ClientListCellRenderer(); } /** * Returns an action which attempts to register with the hub. * Disabled when already registered. * * @return registration action */ public Action createRegisterAction() { Action regAct = new RegisterAction( true ); registerUpdater( regAct, DISABLE_ACTION ); return regAct; } /** * Returns an action which attempts to unregister from the hub. * Disabled when already unregistered. * * @return unregistration action */ public Action createUnregisterAction() { Action unregAct = new RegisterAction( false ); registerUpdater( unregAct, ENABLE_ACTION ); return unregAct; } /** * Returns an action which toggles hub registration. * * @return registration toggler action */ public Action createToggleRegisterAction() { RegisterAction toggleRegAct = new RegisterAction(); registerUpdater( toggleRegAct, new ConnectionUpdate() { public void setConnected( Object item, boolean isConnected ) { ((RegisterAction) item).setSense( ! isConnected ); } } ); return toggleRegAct; } /** * Returns a new action which will register with a hub if one is running, * and if not, will offer to start a hub. * The exact options for starting a hub are given by the * hubStartActions parameter - the elements of this array * will normally be generated by calling the * {@link #createHubAction createHubAction} method. * * @param parent parent component, used for placing dialogue * @param hubStartActions actions which start a hub, * or null for a default list */ public Action createRegisterOrHubAction( final Component parent, Action[] hubStartActions ) { final Action[] hubActs; if ( hubStartActions != null ) { hubActs = hubStartActions; } else { HubServiceMode internalMode = SysTray.getInstance().isSupported() ? HubServiceMode.CLIENT_GUI : HubServiceMode.NO_GUI; hubActs = new Action[] { createHubAction( false, internalMode ), createHubAction( true, HubServiceMode.MESSAGE_GUI ), }; } Action regAct = new RegisterAction() { protected void registerFailed() { Object msg = new String[] { "No SAMP hub is running.", "You may start a hub if you wish.", }; List buttList = new ArrayList(); JButton[] options = new JButton[ hubActs.length + 1 ]; for ( int i = 0; i < hubActs.length; i++ ) { options[ i ] = new JButton( hubActs[ i ] ); } options[ hubActs.length ] = new JButton( "Cancel" ); final JDialog dialog = new JOptionPane( msg, JOptionPane.WARNING_MESSAGE, JOptionPane.DEFAULT_OPTION, null, options, null ) .createDialog( parent, "No Hub" ); ActionListener closeListener = new ActionListener() { public void actionPerformed( ActionEvent evt ) { dialog.dispose(); } }; for ( int iopt = 0; iopt < options.length; iopt++ ) { options[ iopt ].addActionListener( closeListener ); } dialog.setVisible( true ); } }; registerUpdater( regAct, new ConnectionUpdate() { public void setConnected( Object item, boolean isConnected ) { ((RegisterAction) item).setSense( ! isConnected ); } } ); return regAct; } /** * Returns an action which will display a SAMP hub monitor window. * * @return monitor window action */ public Action createShowMonitorAction() { return new MonitorAction(); } /** * Returns an action which will start up a SAMP hub. * You can specify whether it runs in the current JVM or a newly * created one; in the former case, it will shut down when the * current application does. * * @param external false to run in the current JVM, * true to run in a new one * @param hubMode hub mode */ public Action createHubAction( boolean external, HubServiceMode hubMode ) { return new HubAction( external, hubMode ); } /** * Creates a component which indicates whether this connector is currently * connected or not, using supplied icons. * * @param onIcon icon indicating connection * @param offIcon icon indicating no connection * @return connection indicator */ public JComponent createConnectionIndicator( final Icon onIcon, final Icon offIcon ) { JLabel label = new JLabel( new Icon() { private Icon effIcon() { return isConnected() ? onIcon : offIcon; } public int getIconWidth() { return effIcon().getIconWidth(); } public int getIconHeight() { return effIcon().getIconHeight(); } public void paintIcon( Component c, Graphics g, int x, int y ) { effIcon().paintIcon( c, g, x, y ); } } ); registerUpdater( label, REPAINT_COMPONENT ); return label; } /** * Creates a component which indicates whether this connector is currently * connected or not, using default icons. * * @return connection indicator */ public JComponent createConnectionIndicator() { return createConnectionIndicator( new ImageIcon( Client.class .getResource( "images/connected-24.gif" ) ), new ImageIcon( Client.class .getResource( "images/disconnected-24.gif" ) ) ); } /** * Creates a component which shows an icon for each registered client. * * @param vertical true for vertical box, false for horizontal * @param iconSize dimension in pixel of each icon (square) */ public JComponent createClientBox( final boolean vertical, int iconSize ) { final IconStore iconStore = new IconStore( IconStore.createMinimalIcon( iconSize ) ); IconBox box = new IconBox( iconSize ); box.setVertical( vertical ); box.setBorder( createBoxBorder() ); box.setModel( clientListModel_ ); box.setRenderer( new IconBox.CellRenderer() { public Icon getIcon( IconBox iconBox, Object value, int index ) { return IconStore.scaleIcon( iconStore.getIcon( (Client) value ), iconBox.getTransverseSize(), 2.0, ! vertical ); } public String getToolTipText( IconBox iconBox, Object value, int index ) { return ((Client) value).toString(); } } ); Dimension boxSize = box.getPreferredSize(); boxSize.width = 128; box.setPreferredSize( boxSize ); registerUpdater( box, ENABLE_COMPONENT ); return box; } /** * Returns a new component which displays status for this connector. * * @return new hub connection monitor component */ public JComponent createMonitorPanel() { HubView view = new HubView( false ); view.setClientListModel( getClientListModel() ); view.getClientList().setCellRenderer( createClientListCellRenderer() ); return view; } /** * Called when the connection status (registered/unregistered) may have * changed. May be called from any thread. */ private void scheduleConnectionChange() { SwingUtilities.invokeLater( new Runnable() { public void run() { boolean isConnected = isConnected(); if ( isConnected != wasConnected_ ) { wasConnected_ = isConnected; ChangeEvent evt = new ChangeEvent( GuiHubConnector.this ); for ( Iterator it = connectionListenerList_.iterator(); it.hasNext(); ) { ((ChangeListener) it.next()).stateChanged( evt ); } } } } ); } /** * Called when the connection status has changed, or may have changed. */ private void updateConnectionState() { boolean isConn = isConnected(); for ( Iterator it = updateMap_.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); Object item = entry.getKey(); ConnectionUpdate update = (ConnectionUpdate) entry.getValue(); update.setConnected( item, isConn ); } } /** * Returns a border suitable for icon boxes. * * @return border */ static Border createBoxBorder() { return BorderFactory.createCompoundBorder( new JTextField().getBorder(), BorderFactory.createEmptyBorder( 1, 1, 1, 1 ) ); } /** * Adds a given item to the list of objects which will be notified * when the hub is connected/disconnected. By doing it like this * rather than with the usual listener mechanism the problem of * retaining references to otherwise unused listeners is circumvented. * * @param item object to be notified * @param updater object which performs the notification on hub * connect/disconnect */ void registerUpdater( Object item, ConnectionUpdate updater ) { updater.setConnected( item, isConnected() ); updateMap_.put( item, updater ); } /** * Interface defining how an object is to be notified when the hub * connection status changes. */ interface ConnectionUpdate { /** * Invoked when hub connection status changes. * * @param item which is being notified * @param isConnected whether the hub is now connected or not */ void setConnected( Object item, boolean isConnected ); } /** * TrackedClientSet implementation used by this class. * Implements ListModel as well. */ private static class ListModelTrackedClientSet extends TrackedClientSet implements ListModel { private final List clientList_; private final List listenerList_; /** * Constructor. */ ListModelTrackedClientSet() { clientList_ = new ArrayList(); listenerList_ = new ArrayList(); } public int getSize() { return clientList_.size(); } public Object getElementAt( int index ) { return clientList_.get( index ); } public void addListDataListener( ListDataListener listener ) { listenerList_.add( listener ); } public void removeListDataListener( ListDataListener listener ) { listenerList_.remove( listener ); } public void addClient( final Client client ) { super.addClient( client ); SwingUtilities.invokeLater( new Runnable() { public void run() { int index = clientList_.size(); clientList_.add( client ); ListDataEvent evt = new ListDataEvent( ListModelTrackedClientSet.this, ListDataEvent.INTERVAL_ADDED, index, index ); for ( Iterator it = listenerList_.iterator(); it.hasNext(); ) { ((ListDataListener) it.next()).intervalAdded( evt ); } } } ); } public void removeClient( final Client client ) { super.removeClient( client ); SwingUtilities.invokeLater( new Runnable() { public void run() { int index = clientList_.indexOf( client ); assert index >= 0; if ( index >= 0 ) { clientList_.remove( index ); ListDataEvent evt = new ListDataEvent( ListModelTrackedClientSet.this, ListDataEvent.INTERVAL_REMOVED, index, index ); for ( Iterator it = listenerList_.iterator(); it.hasNext(); ) { ((ListDataListener) it.next()) .intervalRemoved( evt ); } } } } ); } public void setClients( final Client[] clients ) { super.setClients( clients ); SwingUtilities.invokeLater( new Runnable() { public void run() { int oldSize = clientList_.size(); if ( oldSize > 0 ) { clientList_.clear(); ListDataEvent removeEvt = new ListDataEvent( ListModelTrackedClientSet.this, ListDataEvent.INTERVAL_REMOVED, 0, oldSize - 1); for ( Iterator it = listenerList_.iterator(); it.hasNext(); ) { ((ListDataListener) it.next()) .intervalRemoved( removeEvt ); } } if ( clients.length > 0 ) { clientList_.addAll( Arrays.asList( clients ) ); int newSize = clientList_.size(); ListDataEvent addEvt = new ListDataEvent( ListModelTrackedClientSet.this, ListDataEvent.INTERVAL_ADDED, 0, newSize - 1); for ( Iterator it = listenerList_.iterator(); it.hasNext(); ) { ((ListDataListener) it.next()) .intervalAdded( addEvt ); } } } } ); } public void updateClient( final Client client, boolean metaChanged, boolean subsChanged ) { super.updateClient( client, metaChanged, subsChanged ); SwingUtilities.invokeLater( new Runnable() { public void run() { int index = clientList_.indexOf( client ); if ( index >= 0 ) { ListDataEvent evt = new ListDataEvent( ListModelTrackedClientSet.this, ListDataEvent.CONTENTS_CHANGED, index, index ); for ( Iterator it = listenerList_.iterator(); it.hasNext(); ) { ((ListDataListener) it.next()) .contentsChanged( evt ); } } } } ); } } /** * Action which registers and unregisters with the hub. */ private class RegisterAction extends AbstractAction { /** * Constructs in an unarmed state. */ public RegisterAction() { } /** * Constructs with a given (initial) sense. * * @param active true to register, false to unregister */ public RegisterAction( boolean active ) { this(); setSense( active ); } /** * Sets whether this action registers or unregisters. * * @param active true to register, false to unregister */ public void setSense( boolean active ) { putValue( ACTION_COMMAND_KEY, active ? "REGISTER" : "UNREGISTER" ); putValue( NAME, active ? "Register with Hub" : "Unregister from Hub" ); putValue( SHORT_DESCRIPTION, active ? "Attempt to connect to SAMP hub" : "Disconnect from SAMP hub" ); } public void actionPerformed( ActionEvent evt ) { String cmd = evt.getActionCommand(); if ( "REGISTER".equals( cmd ) ) { setActive( true ); if ( ! isConnected() ) { registerFailed(); } } else if ( "UNREGISTER".equals( cmd ) ) { setActive( false ); } else { throw new UnsupportedOperationException( "Unknown action " + cmd ); } } protected void registerFailed() { Toolkit.getDefaultToolkit().beep(); } } /** * Action subclass for popping up a monitor window. */ private class MonitorAction extends AbstractAction { private JFrame monitorWindow_; /** * Constructor. */ MonitorAction() { super( "Show Hub Status" ); putValue( SHORT_DESCRIPTION, "Display a window showing client applications" + " registered with the SAMP hub" ); } public void actionPerformed( ActionEvent evt ) { if ( monitorWindow_ == null ) { monitorWindow_ = new JFrame( "SAMP Status" ); monitorWindow_.getContentPane() .add( createMonitorPanel(), BorderLayout.CENTER ); monitorWindow_.pack(); } monitorWindow_.setVisible( true ); } } /** * Action subclass for running a hub. */ private class HubAction extends AbstractAction { private final boolean external_; private final HubServiceMode hubMode_; private final boolean isAvailable_; /** * Constructor. * * @param external false to run in the current JVM, * true to run in a new one * @param hubMode hub mode */ HubAction( boolean external, HubServiceMode hubMode ) { external_ = external; hubMode_ = hubMode; putValue( NAME, "Start " + ( external ? "external" : "internal" ) + " hub" ); putValue( SHORT_DESCRIPTION, "Attempts to start up a SAMP hub" + ( external ? " running independently of this application" : " running within this application" ) ); setEnabled( ! isConnected() ); registerUpdater( this, DISABLE_ACTION ); boolean isAvailable = true; if ( external ) { try { Hub.checkExternalHubAvailability(); } catch ( Exception e ) { isAvailable = false; } } isAvailable_ = isAvailable; } public void actionPerformed( ActionEvent evt ) { try { attemptRunHub(); } catch ( Exception e ) { ErrorDialog.showError( null, "Hub Start Failed", e.getMessage(), e ); } setActive( true ); } public boolean isEnabled() { return isAvailable_ && super.isEnabled(); } /** * Tries to start a hub, but may throw an exception. */ private void attemptRunHub() throws IOException { if ( external_ ) { Hub.runExternalHub( hubMode_ ); } else { Hub.runHub( hubMode_ ); } setActive( true ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/GuiHubService.java000066400000000000000000000214701356450004300253400ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.util.Map; import java.util.Random; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.hub.BasicHubService; import org.astrogrid.samp.hub.ClientSet; import org.astrogrid.samp.hub.HubClient; /** * BasicHubService subclass which provides a GUI window displaying hub * status as well as the basic hub services. * * @author Mark Taylor * @since 16 Jul 2008 */ public class GuiHubService extends BasicHubService { private GuiClientSet clientSet_; private ListSelectionModel selectionModel_; /** * Constructor. * * @param random random number generator used for message tags etc */ public GuiHubService( Random random ) { super( random ); } public void start() { super.start(); clientSet_ = (GuiClientSet) getClientSet(); } protected ClientSet createClientSet() { return new GuiClientSet( getIdComparator() ); } /** * Creates a new component containing a display of the current hub * internal state. * * @return new hub viewer panel */ public JComponent createHubPanel() { HubView hubView = new HubView( true ); hubView.setClientListModel( getClientListModel() ); JList jlist = hubView.getClientList(); jlist.setCellRenderer( new ClientListCellRenderer() ); jlist.addMouseListener( new HubClientPopupListener( this ) ); selectionModel_ = jlist.getSelectionModel(); return hubView; } /** * Creates a new window which maintains a display of the current hub * internal state. * * @return new hub viewer window */ public JFrame createHubWindow() { JFrame frame = new JFrame( "SAMP Hub" ); frame.getContentPane().add( createHubPanel() ); frame.setIconImage( new ImageIcon( Client.class .getResource( "images/hub.png" ) ) .getImage() ); frame.pack(); return frame; } protected void declareMetadata( HubClient caller, Map meta ) throws SampException { super.declareMetadata( caller, meta ); clientSet_.scheduleClientChanged( caller ); } protected void declareSubscriptions( HubClient caller, Map subscriptions ) throws SampException { super.declareSubscriptions( caller, subscriptions ); clientSet_.scheduleClientChanged( caller ); } /** * Returns a ListModel containing information about clients currently * registered with this hub. * * @return list model in which each element is a * {@link org.astrogrid.samp.Client} */ public ListModel getClientListModel() { return clientSet_; } /** * Returns the selection model corresponding to this service's client * list model. * * @return list selection model for client selection */ public ListSelectionModel getClientSelectionModel() { return selectionModel_; } /** * Returns the client object currently selected in the GUI, if any. * * @return currently selected client, or null */ private Client getSelectedClient() { ListSelectionModel selModel = getClientSelectionModel(); int isel = selModel.getMinSelectionIndex(); Object selected = isel >= 0 ? getClientListModel().getElementAt( isel ) : null; return selected instanceof Client ? (Client) selected : null; } /** * Returns an array of menus which may be added to a window * containing this service's window. * * @return menu array */ public JMenu[] createMenus() { final HubConnection serviceConnection = getServiceConnection(); final String hubId = serviceConnection.getRegInfo().getSelfId(); /* Broadcast ping action. */ final Message pingMessage = new Message( "samp.app.ping" ); final Action pingAllAction = new AbstractAction( "Ping all" ) { public void actionPerformed( ActionEvent evt ) { new SampThread( evt, "Ping Error", "Error broadcasting ping" ) { protected void sampRun() throws SampException { serviceConnection.callAll( "ping-tag", pingMessage ); } }.start(); } }; pingAllAction.putValue( Action.SHORT_DESCRIPTION, "Send ping message to all clients" ); /* Single client ping action. */ final String pingSelectedName = "Ping selected client"; final Action pingSelectedAction = new AbstractAction( pingSelectedName ) { public void actionPerformed( ActionEvent evt ) { final Client client = getSelectedClient(); if ( client != null ) { new SampThread( evt, "Ping Error", "Error sending ping to " + client ) { protected void sampRun() throws SampException { serviceConnection.call( client.getId(), "ping-tag", pingMessage ); } }.start(); } } }; pingSelectedAction.putValue( Action.SHORT_DESCRIPTION, "Send ping message to selected client" ); /* Single client disconnect action. */ final String disconnectSelectedName = "Disconnect selected client"; final Action disconnectSelectedAction = new AbstractAction( disconnectSelectedName ) { public void actionPerformed( ActionEvent evt ) { final Client client = getSelectedClient(); if ( client != null ) { new SampThread( evt, "Disconnect Error", "Error disconnecting " + client ) { protected void sampRun() throws SampException { disconnect( client.getId(), "GUI hub user requested ejection" ); } }.start(); } } }; disconnectSelectedAction.putValue( Action.SHORT_DESCRIPTION, "Forcibly disconnect selected client" + " from the hub" ); /* Ensure that actions are kept up to date. */ ListSelectionListener selListener = new ListSelectionListener() { public void valueChanged( ListSelectionEvent evt ) { Client client = getSelectedClient(); boolean isSel = client != null; boolean canPing = isSel && client.getSubscriptions() .isSubscribed( pingMessage.getMType() ); boolean canDisco = isSel && ! hubId.equals( client.getId() ); pingSelectedAction.setEnabled( canPing ); disconnectSelectedAction.setEnabled( canDisco ); String clientDesignation = client == null ? "" : ( " (" + client + ")" ); pingSelectedAction.putValue( Action.NAME, pingSelectedName + clientDesignation ); disconnectSelectedAction.putValue( Action.NAME, disconnectSelectedName + clientDesignation ); } }; getClientSelectionModel().addListSelectionListener( selListener ); selListener.valueChanged( null ); /* Prepare and return menus containing the actions. */ JMenu clientMenu = new JMenu( "Clients" ); clientMenu.setMnemonic( KeyEvent.VK_C ); clientMenu.add( new JMenuItem( pingAllAction ) ); clientMenu.add( new JMenuItem( pingSelectedAction ) ); clientMenu.add( new JMenuItem( disconnectSelectedAction ) ); return new JMenu[] { clientMenu }; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/HubClientPopupListener.java000066400000000000000000000151751356450004300272500ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.AbstractAction; import javax.swing.JList; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import org.astrogrid.samp.Message; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.hub.HubClient; import org.astrogrid.samp.hub.BasicHubService; /** * MouseListener which provides a popup menu with per-client options * for use with a JList containing HubClient objects. * * @author Mark Taylor * @since 8 Jul 2009 */ class HubClientPopupListener implements MouseListener { private final BasicHubService hub_; /** Message which does a ping. */ private static final Message PING_MSG = new Message( "samp.app.ping" ); /** * Constructor. * * @param hub hub service which knows about the HubClients contained * in the JList this will be listening to */ public HubClientPopupListener( BasicHubService hub ) { hub_ = hub; } public void mouseClicked( MouseEvent evt ) { } public void mouseEntered( MouseEvent evt ) { } public void mouseExited( MouseEvent evt ) { } public void mousePressed( MouseEvent evt ) { maybeShowPopup( evt ); } public void mouseReleased( MouseEvent evt ) { maybeShowPopup( evt ); } /** * Invoked for a MouseEvent which may be a popup menu trigger. * * @param evt popup trigger event candidate */ private void maybeShowPopup( MouseEvent evt ) { if ( evt.isPopupTrigger() && evt.getSource() instanceof JList ) { final JList jlist = (JList) evt.getSource(); final int index = jlist.locationToIndex( evt.getPoint() ); if ( index >= 0 ) { Object item = jlist.getModel().getElementAt( index ); if ( item instanceof HubClient ) { HubClient client = (HubClient) item; // Set the selection to the client for which the menu // will be posted. This is not essential, but it can be // visually confusing for the user if it doesn't happen. SwingUtilities.invokeLater( new Runnable() { public void run() { jlist.setSelectedIndex( index ); } } ); Component comp = evt.getComponent(); JPopupMenu popper = createPopup( comp, client ); popper.show( comp, evt.getX(), evt.getY() ); } } } } /** * Returns a new popup menu for a given client. * The actions on this menu are not dynamic (e.g. do not enable/disable * themselves according to changes in the hub status) because the * menu is likely to be short-lived. * * @param parent parent component * @param client hub client which the menu will affect * @return new popup menu */ private JPopupMenu createPopup( Component parent, HubClient client ) { JPopupMenu popper = new JPopupMenu(); popper.add( new CallAction( parent, client, "Ping", PING_MSG, true ) ); popper.add( new DisconnectAction( parent, client ) ); return popper; } /** * Action which will forcibly disconnect a given client. */ private class DisconnectAction extends AbstractAction { private final Component parent_; private final HubClient client_; /** * Constructor. * * @param parent parent component * @param client client to disconnect */ public DisconnectAction( Component parent, HubClient client ) { super( "Disconnect" ); parent_ = parent; client_ = client; putValue( SHORT_DESCRIPTION, "Forcibly disconnect client " + client_ + " from hub" ); setEnabled( ! client.getId() .equals( hub_.getServiceConnection() .getRegInfo().getSelfId() ) ); } public void actionPerformed( ActionEvent evt ) { new SampThread( parent_, "Disconnect Error", "Error disconnecting client " + client_ ) { protected void sampRun() throws SampException { hub_.disconnect( client_.getId(), "GUI hub user requested ejection" ); } }.start(); } } /** * Action which will send a message to a client. */ private class CallAction extends AbstractAction { private final Component parent_; private final HubClient client_; private final String name_; private final Message msg_; private final boolean isCall_; /** * Constructor. * * @param parent parent component * @param client client to receive message * @param name informal name of message (for menu) * @param msg message to send * @param isCall true for call, false for notify */ public CallAction( Component parent, HubClient client, String name, Message msg, boolean isCall ) { super( name ); parent_ = parent; client_ = client; name_ = name; msg_ = msg; isCall_ = isCall; String mtype = msg.getMType(); putValue( SHORT_DESCRIPTION, "Send " + mtype + ( isCall ? " call" : " notification" ) + " to client " + client ); setEnabled( client_.isSubscribed( mtype ) ); } public void actionPerformed( ActionEvent evt ) { final HubConnection connection = hub_.getServiceConnection(); final String recipientId = client_.getId(); new SampThread( parent_, name_ + " Error", "Error attempting to send message " + msg_.getMType() + " to client " + client_ ) { protected void sampRun() throws SampException { if ( isCall_ ) { connection.call( recipientId, name_ + "-tag", msg_ ); } else { connection.notify( recipientId, msg_ ); } } }.start(); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/HubMonitor.java000066400000000000000000000237631356450004300247310ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.HubConnector; import org.astrogrid.samp.client.MessageHandler; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.UtilServer; /** * Client application which uses a {@link GuiHubConnector} * to connect to any running hub and display information about all currently * registered clients. * * @author Mark Taylor * @since 16 Jul 2008 */ public class HubMonitor extends JPanel { private final GuiHubConnector connector_; private static Logger logger_ = Logger.getLogger( HubMonitor.class.getName() ); /** * Constructor. * * @param profile SAMP profile * @param trackMessages if true, the GUI will contain a visual * representation of messages sent and received * @param autoSec number of seconds between automatic hub connection * attempts; <=0 means no automatic connections */ public HubMonitor( ClientProfile profile, boolean trackMessages, int autoSec ) { super( new BorderLayout() ); // Set up a new GuiHubConnector and GUI decorations. connector_ = trackMessages ? new MessageTrackerHubConnector( profile ) : new GuiHubConnector( profile ); // Declare the default subscriptions. This is required so that // the hub knows the client is subscribed to those hub.event // MTypes which inform about client registration, hub shutdown etc. connector_.declareSubscriptions( connector_.computeSubscriptions() ); // Declare metadata about this application. Metadata meta = new Metadata(); meta.setName( "HubMonitor" ); meta.setDescriptionText( "GUI hub monitor utility" ); try { meta.setIconUrl( UtilServer.getInstance() .exportResource( "/org/astrogrid/samp/images/" + "eye.gif" ) .toString() ); } catch ( IOException e ) { logger_.warning( "Can't set icon" ); } meta.put( "author", "Mark Taylor" ); connector_.declareMetadata( meta ); // Create and place a component which maintains a display of // currently registered clients. A more modest GUI could just use // connector.getClientListModel() as a model for a JList component. add( connector_.createMonitorPanel(), BorderLayout.CENTER ); // Prepare a container for other widgets at the bottom of the window. JPanel infoBox = new JPanel( new BorderLayout() ); add( infoBox, BorderLayout.SOUTH ); // Create and place components which allow the user to // view and control registration/unregistration explicitly. JComponent connectBox = new JPanel( new BorderLayout() ); connectBox.add( new JButton( connector_.createToggleRegisterAction() ), BorderLayout.CENTER ); connectBox.add( connector_.createConnectionIndicator(), BorderLayout.EAST ); infoBox.add( connectBox, BorderLayout.EAST ); // Create and place components which provide a compact display // of the connector's status. JComponent statusBox = Box.createHorizontalBox(); statusBox.add( connector_.createClientBox( false, 24 ) ); if ( connector_ instanceof MessageTrackerHubConnector ) { statusBox.add( ((MessageTrackerHubConnector) connector_) .createMessageBox( 24 ) ); } infoBox.add( statusBox, BorderLayout.CENTER ); // Attempt registration, and arrange that if/when unregistered we look // for a hub to register with on a regular basis. connector_.setActive( true ); connector_.setAutoconnect( autoSec ); } /** * Returns this monitor's HubConnector. * * @return hub connector */ public GuiHubConnector getHubConnector() { return connector_; } /** * Does the work for the main method. */ public static int runMain( String[] args ) { String usage = new StringBuffer() .append( "\n Usage:" ) .append( "\n " ) .append( HubMonitor.class.getName() ) .append( "\n " ) .append( " [-help]" ) .append( " [+/-verbose]" ) .append( "\n " ) .append( " [-auto ]" ) .append( " [-nomsg]" ) .append( " [-nogui]" ) .append( "\n " ) .append( " [-mtype ]" ) .append( "\n" ) .toString(); List argList = new ArrayList( Arrays.asList( args ) ); int verbAdjust = 0; boolean gui = true; boolean trackMsgs = true; int autoSec = 3; Subscriptions subs = new Subscriptions(); for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.startsWith( "-auto" ) && it.hasNext() ) { it.remove(); String sauto = (String) it.next(); it.remove(); autoSec = Integer.parseInt( sauto ); } else if ( arg.equals( "-gui" ) ) { it.remove(); gui = true; } else if ( arg.equals( "-nogui" ) ) { it.remove(); gui = false; } else if ( arg.equals( "-msg" ) ) { it.remove(); trackMsgs = true; } else if ( arg.equals( "-nomsg" ) ) { it.remove(); trackMsgs = false; } else if ( arg.startsWith( "-mtype" ) && it.hasNext() ) { it.remove(); String mpat = (String) it.next(); it.remove(); subs.addMType( mpat ); } else if ( arg.startsWith( "-v" ) ) { it.remove(); verbAdjust--; } else if ( arg.startsWith( "+v" ) ) { it.remove(); verbAdjust++; } else if ( arg.startsWith( "-h" ) ) { it.remove(); System.out.println( usage ); return 0; } else { it.remove(); System.err.println( usage ); return 1; } } assert argList.isEmpty(); // Adjust logging in accordance with verboseness flags. int logLevel = Level.WARNING.intValue() + 100 * verbAdjust; Logger.getLogger( "org.astrogrid.samp" ) .setLevel( Level.parse( Integer.toString( logLevel ) ) ); // Get profile. final ClientProfile profile =DefaultClientProfile.getProfile(); // Create the HubMonitor. final HubMonitor monitor = new HubMonitor( profile, trackMsgs, autoSec ); // Add a handler for extra MTypes if so requested. if ( ! subs.isEmpty() ) { final Subscriptions extraSubs = subs; HubConnector connector = monitor.getHubConnector(); final Response dummyResponse = new Response(); dummyResponse.setStatus( Response.WARNING_STATUS ); dummyResponse.setResult( new HashMap() ); dummyResponse.setErrInfo( new ErrInfo( "Message logged, " + "no other action taken" ) ); connector.addMessageHandler( new MessageHandler() { public Map getSubscriptions() { return extraSubs; } public void receiveNotification( HubConnection connection, String senderId, Message msg ) { } public void receiveCall( HubConnection connection, String senderId, String msgId, Message msg ) throws SampException { connection.reply( msgId, dummyResponse ); } } ); connector.declareSubscriptions( connector.computeSubscriptions() ); } // Start the gui in a new window. final boolean isVisible = gui; SwingUtilities.invokeLater( new Runnable() { public void run() { JFrame frame = new JFrame( "SAMP HubMonitor" ); frame.getContentPane().add( monitor ); frame.setIconImage( new ImageIcon( Metadata.class .getResource( "images/eye.gif" ) ) .getImage() ); frame.pack(); frame.setVisible( isVisible ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); } } ); return 0; } /** * Displays a HubMonitor in a window. * Use -help flag. */ public static void main( String[] args ) { int status = runMain( args ); if ( status != 0 ) { System.exit( status ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/HubView.java000066400000000000000000000116651356450004300242120ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.awt.Dimension; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.Metadata; /** * Component displaying a list of SAMP {@link org.astrogrid.samp.Client}s, * usually those registered with a hub. * * @author Mark Taylor * @since 16 Jul 2008 */ public class HubView extends JPanel { private final JList jlist_; private final ClientPanel clientPanel_; private final ListDataListener listListener_; /** * Constructor. * * @param hubLike true if this will be displaying clients implementing * the HubClient interface */ public HubView( boolean hubLike ) { super( new BorderLayout() ); // Set up a JList to display the list of clients. // If a selection is made, update the client detail panel. jlist_ = new JList(); jlist_.setCellRenderer( new ClientListCellRenderer() ); ListSelectionModel selModel = jlist_.getSelectionModel(); selModel.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); selModel.addListSelectionListener( new ListSelectionListener() { public void valueChanged( ListSelectionEvent evt ) { if ( ! evt.getValueIsAdjusting() ) { updateClientView(); } } } ); // Watch the list; if any change occurs which may affect the currently- // selected client, update the client detail panel. listListener_ = new ListDataListener() { public void contentsChanged( ListDataEvent evt ) { preferSelection(); int isel = jlist_.getSelectedIndex(); int i0 = evt.getIndex0(); int i1 = evt.getIndex1(); if ( isel >= 0 && ( i0 < 0 || i1 < 0 || ( i0 - isel ) * ( i1 - isel ) <= 0 ) ) { updateClientView(); } } public void intervalRemoved( ListDataEvent evt ) { if ( clientPanel_.getClient() != null && jlist_.getSelectedIndex() < 0 ) { updateClientView(); } } public void intervalAdded( ListDataEvent evt ) { preferSelection(); } }; // Construct and place subcomponents. clientPanel_ = new ClientPanel( hubLike ); JSplitPane splitter = new JSplitPane(); splitter.setOneTouchExpandable( true ); JScrollPane listScroller = new JScrollPane( jlist_ ); listScroller.setPreferredSize( new Dimension( 200, 400 ) ); listScroller.setBorder( ClientPanel.createTitledBorder( "Clients" ) ); splitter.setLeftComponent( listScroller ); splitter.setRightComponent( clientPanel_ ); add( splitter ); } /** * Sets the client list model which is displayed in this component. * * @param clientModel list model whose elements are * {@link org.astrogrid.samp.Client}s */ public void setClientListModel( ListModel clientModel ) { ListModel oldModel = jlist_.getModel(); jlist_.getSelectionModel().clearSelection(); if ( oldModel != null ) { oldModel.removeListDataListener( listListener_ ); } jlist_.setModel( clientModel ); if ( clientModel != null ) { clientModel.addListDataListener( listListener_ ); preferSelection(); } } /** * Returns the JList component which houses the active list of clients. * This can be manipulated to, for instance, customise the renderer * if required. Its model should not be set directly however; use the * {@link #setClientListModel} method. * * @return client JList */ public JList getClientList() { return jlist_; } /** * Ensure that the client panel is up to date with respect to the currently * selected client. */ private void updateClientView() { int isel = jlist_.getSelectedIndex(); clientPanel_.setClient( isel >= 0 ? (Client) jlist_.getModel().getElementAt( isel ) : null ); } /** * Invoked when the list may have just acquired more than zero elements * to select one rather than none of them. */ private void preferSelection() { if ( jlist_.getSelectedIndex() < 0 && jlist_.getModel().getSize() > 0 ) { jlist_.setSelectedIndex( 0 ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/IconBox.java000066400000000000000000000401721356450004300241750ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Point; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.swing.AbstractListModel; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.ListModel; import javax.swing.ToolTipManager; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; /** * Component which displays the contents of a ListModel as icons. * Custom icon and tooltip generation are supported by use of a separate * renderer object. * * @author Mark Taylor * @since 26 Nov 2008 */ class IconBox extends JComponent { private final List entryList_; private final ListDataListener modelListener_; private final Color enabledBg_; private final Color disabledBg_; private boolean vertical_; private boolean trailing_; private boolean reversed_; private int transSize_; private int gap_; private CellRenderer renderer_; private ListModel model_; private Dimension minSize_; private Dimension maxSize_; private Dimension prefSize_; private static final ListModel EMPTY_LIST_MODEL = createEmptyListModel(); /** * Constructor. * * @param transSize the transverse (horizontal/vertical) size * available for icons in pixels */ public IconBox( int transSize ) { transSize_ = transSize; setOpaque( true ); disabledBg_ = null; enabledBg_ = Color.WHITE; setBackground( enabledBg_ ); modelListener_ = new ListDataListener() { public void contentsChanged( ListDataEvent evt ) { int i0 = evt.getIndex0(); int i1 = evt.getIndex1(); if ( 0 <= i0 && i0 <= i1 && i1 <= entryList_.size() ) { for ( int i = i0; i <= i1; i++ ) { entryList_.set( i, createEntry( i ) ); } repaint(); // could be more efficient } else { refreshState(); } } public void intervalAdded( ListDataEvent evt ) { int i0 = evt.getIndex0(); int i1 = evt.getIndex1(); if ( 0 <= i0 && i0 <= i1 && i1 <= model_.getSize() ) { for ( int i = i0; i <= i1; i++ ) { entryList_.add( i, createEntry( i ) ); } repaint(); // could be more efficient } else { refreshState(); } } public void intervalRemoved( ListDataEvent evt ) { int i0 = evt.getIndex0(); int i1 = evt.getIndex1(); if ( 0 <= i0 && i0 <= i1 && i1 <= entryList_.size() ) { for ( int i = i1; i >= i0; i-- ) { entryList_.remove( i ); } repaint(); // could be more efficient } else { refreshState(); } } }; entryList_ = new ArrayList(); gap_ = 4; ToolTipManager.sharedInstance().registerComponent( this ); setModel( EMPTY_LIST_MODEL ); setRenderer( new DefaultRenderer() ); } /** * Sets whether icons will be lined up in a horizontal or vertical line. * * @param vertical true for vertical run, false for horizontal */ public void setVertical( boolean vertical ) { vertical_ = vertical; revalidate(); repaint(); } /** * Returns whether icons will be lined up horizontally or vertically. * * @return false for hormizontal run, true for vertical */ public boolean getVertical() { return vertical_; } /** * Sets the alignment of the icons in this component. * * @param trailing false for left/top, true for right/bottom alignment */ public void setTrailing( boolean trailing ) { trailing_ = trailing; repaint(); } /** * Returns the alignment of the icons in this component. * * @return false for left/top, true for right/bottom alignment */ public boolean getTrailing() { return trailing_; } /** * Sets the first-to-last ordering of the icons in this component. * * @param reversed false for increasing to right/bottom, * true for increasig to left/top */ public void setReversed( boolean reversed ) { reversed_ = reversed; repaint(); } /** * Returns the first-to-last ordering of the icons in this component. * * @return false for increasing to right/bottom, * true for increasig to left/top */ public boolean getReversed() { return reversed_; } public void setEnabled( boolean enabled ) { super.setEnabled( enabled ); setBackground( enabled ? enabledBg_ : disabledBg_ ); } /** * Refreshes the list-related state from scratch. */ private void refreshState() { entryList_.clear(); int count = model_.getSize(); for ( int i = 0; i < count; i++ ) { entryList_.add( createEntry( i ) ); } repaint(); } /** * Constructs an Entry object from an object contained in the ListModel, * using the currently installed renderer. * * @param index index of entry in list * @return new entry */ private Entry createEntry( int index ) { Object value = model_.getElementAt( index ); return new Entry( renderer_.getIcon( this, value, index ), renderer_.getToolTipText( this, value, index ) ); } /** * Sets the list model for use with this component. * Objects will be rendered as icons by using the currently intalled * renderer. * * @param model list model */ public void setModel( ListModel model ) { if ( model_ != null ) { model_.removeListDataListener( modelListener_ ); } model_ = model; if ( model_ != null ) { model_.addListDataListener( modelListener_ ); } refreshState(); } /** * Returns the list model used by this component. * * @return list model */ public ListModel getModel() { return model_; } /** * Sets the transverse dimension in pixels of this box. * * @param transSize pixel count across list run */ public void setTransverseSize( int transSize ) { transSize_ = transSize; if ( transSize_ != transSize ) { repaint(); } } /** * Returns the transverse dimension in pixels of this box. * * @return pixel count across run */ public int getTransverseSize() { return transSize_; } /** * Sets the object which is used to turn list model contents into the * icons and tooltips displayed by this component. * * @param renderer new renderer */ public void setRenderer( CellRenderer renderer ) { renderer_ = renderer; refreshState(); } /** * Returns the object which turns list model contents into display things. * The default value tries to cast to Icon and uses toString for tooltip. * * @return current renderer */ public CellRenderer getRenderer() { return renderer_; } public void setPreferredSize( Dimension prefSize ) { prefSize_ = prefSize; } public Dimension getPreferredSize() { if ( prefSize_ == null ) { int leng = 0; for ( Iterator it = entryList_.iterator(); it.hasNext(); ) { Entry entry = (Entry) it.next(); Icon icon = entry.icon_; leng += vertical_ ? icon.getIconHeight() : icon.getIconWidth(); if ( it.hasNext() ) { leng += gap_; } } Dimension size = vertical_ ? new Dimension( transSize_, leng ) : new Dimension( leng, transSize_ ); Insets insets = getInsets(); size.width += insets.left + insets.right; size.height += insets.top + insets.bottom; return size; } else { return prefSize_; } } public void setMinimumSize( Dimension minSize ) { minSize_ = minSize; } public Dimension getMinimumSize() { if ( minSize_ == null ) { Dimension size = vertical_ ? new Dimension( transSize_, 0 ) : new Dimension( 0, transSize_ ); Insets insets = getInsets(); size.width += insets.left + insets.right; size.height += insets.top + insets.bottom; return size; } else { return minSize_; } } public void setMaximumSize( Dimension maxSize ) { maxSize_ = maxSize; } public Dimension getMaximumSize() { if ( maxSize_ == null ) { Dimension size = new Dimension( transSize_, transSize_ ); Insets insets = getInsets(); size.width += insets.left + insets.right; size.height += insets.top + insets.bottom; if ( vertical_ ) { size.height = Integer.MAX_VALUE; } else { size.width = Integer.MAX_VALUE; } return size; } else { return maxSize_; } } /** * Returns the index of the list model element whose icon is displayed * at a given point in this component. * * @param point point to interrogate * @return list model index, or -1 if not found */ public int getIndexAt( Point point ) { Dimension size = getSize(); Insets insets = getInsets(); // The following should reject the request if it's outside this components // bounds. However, it seems that sometimes (always??) getSize() reports // zero size. I don't understand why, and I'm surprised the rest of the // functionality here works under these circumstances; but it does. // Leave it like this for now. // if ( point.x < insets.left || point.x > size.width - insets.right || // point.y < insets.top || point.y > size.height - insets.bottom ) { // return -1; // } List entryList = entryList_; if ( reversed_ ) { entryList = new ArrayList( entryList ); Collections.reverse( entryList ); } int pLeng = trailing_ ? ( vertical_ ? size.height - insets.bottom - point.y : size.width - insets.right - point.x ) : ( vertical_ ? point.y - insets.top : point.x - insets.left ); int index = 0; for ( Iterator it = entryList.iterator(); it.hasNext(); ) { Icon icon = ((Entry) it.next()).icon_; int leng = gap_ + ( vertical_ ? icon.getIconHeight() : icon.getIconWidth() ); if ( pLeng < leng ) { if ( index < entryList.size() ) { return index; } else { assert false; return -1; } } pLeng -= leng; index++; } return -1; } protected void paintComponent( Graphics g ) { super.paintComponent( g ); Dimension size = getSize(); if ( isOpaque() ) { Color color = g.getColor(); g.setColor( getBackground() ); g.fillRect( 0, 0, size.width, size.height ); g.setColor( color ); } Insets insets = getInsets(); List entryList = entryList_; if ( reversed_ ) { entryList = new ArrayList( entryList ); Collections.reverse( entryList ); } if ( entryList.isEmpty() ) { return; } int x = vertical_ ? insets.left : ( trailing_ ? size.width - insets.right - ((Entry) entryList.get( 0 )) .icon_.getIconWidth() : insets.left ); int y = vertical_ ? ( trailing_ ? size.height - insets.bottom - ((Entry) entryList.get( 0 )) .icon_.getIconHeight() : insets.top ) : insets.top; for ( Iterator it = entryList.iterator(); it.hasNext(); ) { Icon icon = ((Entry) it.next()).icon_; int width = icon.getIconWidth(); int height = icon.getIconHeight(); if ( g.hitClip( x, y, width, height ) ) { icon.paintIcon( this, g, x, y ); } if ( vertical_ ) { y += ( trailing_ ? -1 : +1 ) * ( height + gap_ ); } else { x += ( trailing_ ? -1 : +1 ) * ( width + gap_ ); } } } public String getToolTipText( MouseEvent evt ) { int index = getIndexAt( evt.getPoint() ); return index >= 0 ? ((Entry) entryList_.get( index )).tooltip_ : null; } /** * Defines how list model elements will be rendered as icons and tooltips. */ interface CellRenderer { /** * Returns the icon to be displayed for a given list model element. * * @param iconBox component using this renderer * @param value list model element * @param index index in the entry list being rendered * @return icon to paint */ Icon getIcon( IconBox iconBox, Object value, int index ); /** * Returns the tooltip text to be used for a given list model element. * Null is OK. * * @param iconBox component using this renderer * @param value list model element * @param index index in the entry list being rendered * @return tooltip for value */ String getToolTipText( IconBox iconBox, Object value, int index ); } /** * Convenience struct-type class which aggregates an icon and a tooltip. */ private static class Entry { final Icon icon_; final String tooltip_; /** * Constructor. * * @param icon icon * @param tooltip tooltip */ Entry( Icon icon, String tooltip ) { icon_ = icon; tooltip_ = tooltip; } } /** * Constructs an immutable list model with no content. * * @return dummy list model */ private static ListModel createEmptyListModel() { return new AbstractListModel() { public int getSize() { return 0; } public Object getElementAt( int index ) { return null; } }; } /** * Default renderer. */ private class DefaultRenderer implements CellRenderer, Icon { public Icon getIcon( IconBox iconBox, Object value, int index ) { return value instanceof Icon ? (Icon) value : (Icon) this; } public String getToolTipText( IconBox iconBox, Object value, int index ) { return value == null ? null : value.toString(); } public int getIconWidth() { return transSize_; } public int getIconHeight() { return transSize_; } public void paintIcon( Component c, Graphics g, int x, int y ) { g.drawOval( x, y, transSize_, transSize_ ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/IconStore.java000066400000000000000000000261321356450004300245410ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.AffineTransform; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import javax.swing.Icon; import javax.swing.ImageIcon; import org.astrogrid.samp.Client; import org.astrogrid.samp.Metadata; /** * Manages client icons. Images are cached where appropriate. * A size may be supplied so that all icons returned by this object's methods * are of a given standard size. * Also provides some icon utility methods. * * @author Mark Taylor * @since 17 Nov 2008 */ public class IconStore { private final Icon defaultIcon_; private static final Map urlIconMap_ = new HashMap(); private static final Logger logger_ = Logger.getLogger( IconStore.class.getName() ); /** * Constructor. * * @param defaultIcon icon returned if no client icon is available */ public IconStore( Icon defaultIcon ) { defaultIcon_ = defaultIcon; } /** * Returns the icon supplied by the graphic file at a given URL. * Icons are cached, so that repeated invocations with the same url * are not expensive. * * @param url URL of image * @return image icon, resized if appropriate */ public Icon getIcon( String url ) { if ( ! urlIconMap_.containsKey( url ) ) { try { Icon icon = readIcon( url, 5 ); synchronized ( urlIconMap_ ) { urlIconMap_.put( url, icon ); } } catch ( IOException e ) { logger_.warning( "Icon not found \"" + url + "\" " + e ); synchronized ( urlIconMap_ ) { urlIconMap_.put( url, defaultIcon_ ); } } } Icon icon = (Icon) urlIconMap_.get( url ); if ( icon.getIconWidth() < 0 ) { icon = defaultIcon_; } return icon; } /** * Returns the icon associated with a given client. * This is either the icon described in its metadata or the default icon * if there isn't one. * * @param client client whose icon is required * @return associated icon, resized if appropriate */ public Icon getIcon( Client client ) { Metadata meta = client.getMetadata(); if ( meta != null ) { Object url = meta.get( Metadata.ICONURL_KEY ); if ( url instanceof String ) { return getIcon( (String) url ); } } return defaultIcon_; } /** * Returns an icon with no content but a given size. * * @param size edge size in pixels * @return emtpy square icon */ public static Icon createEmptyIcon( final int size ) { return new Icon() { public int getIconWidth() { return size; } public int getIconHeight() { return size; } public void paintIcon( Component c, Graphics g, int x, int y ) { } }; } /** * Returns an icon which indicates a shape but doesn't look like much. * Currently it's a kind of open square. * * @param size dimension in pixels * @return minimal icon */ public static Icon createMinimalIcon( final int size ) { return new Icon() { int gap = 2; int size = 24; public int getIconWidth() { return size; } public int getIconHeight() { return size; } public void paintIcon( Component c, Graphics g, int x, int y ) { Color color = g.getColor(); int nl = 5; for ( int i = 0; i < nl; i++ ) { int lo = gap + i; int dim = size - 2 * ( gap + i ); if ( dim <= 0 ) { break; } int glevel = 255 * ( i + 1 ) / ( nl + 1 ); g.setColor( new Color( glevel, glevel, glevel ) ); g.drawRect( x + lo, y + lo, dim, dim ); } g.setColor( color ); } }; } /** * Constructs an icon given a file name in the images directory. * * @param fileName file name omitting directory * @return icon */ static Icon createResourceIcon( String fileName ) { String relLoc = "images/" + fileName; URL resource = Client.class.getResource( relLoc ); if ( resource != null ) { return new ImageIcon( resource ); } else { logger_.warning( "Failed to load icon " + relLoc ); return new Icon() { public int getIconWidth() { return 24; } public int getIconHeight() { return 24; } public void paintIcon( Component c, Graphics g, int x, int y ) { } }; } } /** * Return an icon based on an existing one, but drawn to an exact size. * * @param icon original icon, or null for blank * @param size number of horizontal and vertical pixels in output * @return resized version of icon */ public static Icon sizeIcon( Icon icon, final int size ) { if ( icon == null ) { return new Icon() { public int getIconWidth() { return size; } public int getIconHeight() { return size; } public void paintIcon( Component c, Graphics g, int x, int y ) { } }; } else if ( icon.getIconWidth() == size && icon.getIconHeight() == size ) { return icon; } else { return new SizedIcon( icon, size ); } } /** * Icon implementation which is rescaled to so that one dimension * (either width or height) has a fixed value. * * @param icon input icon * @param fixDim the fixed dimension in pixels * @param maxAspect maximum aspect ratio (>= 1) * @param fixVertical true to fix height, false to fix width */ public static Icon scaleIcon( final Icon icon, final int fixDim, double maxAspect, boolean fixVertical ) { final int w = icon.getIconWidth(); final int h = icon.getIconHeight(); if ( ( fixVertical ? h : w ) == fixDim && ( fixVertical ? h / (double) w : w / (double) h ) <= maxAspect ) { return icon; } double factor = fixDim / (double) ( fixVertical ? h : w ); if ( factor > 1.0 && factor < 2.0 ) { factor = 1.0; } double aspect = factor * ( fixVertical ? h : w ) / fixDim; if ( aspect > maxAspect ) { factor *= maxAspect / aspect; } final int width = fixVertical ? (int) Math.ceil( factor * w ) : fixDim; final int height = fixVertical ? fixDim : (int) Math.ceil( factor * h ); final double fact = factor; return new Icon() { public int getIconWidth() { return width; } public int getIconHeight() { return height; } public void paintIcon( Component c, Graphics g, int x, int y ) { if ( fact == 1.0 ) { icon.paintIcon( c, g, x + ( width - w ) / 2, y + ( height - h ) / 2 ); } else { Graphics2D g2 = (Graphics2D) g; AffineTransform trans = g2.getTransform(); g2.translate( x + ( width - w * fact ) / 2, y + ( height - h * fact ) / 2 ); g2.scale( fact, fact ); icon.paintIcon( c, g2, 0, 0 ); g2.setTransform( trans ); } } }; } /** * Reads an icon from a URL, with a maximum wait time. * If the timeout is exceeded, an exception will be thrown. * * @param url icon URL * @param waitSecs maximum time in seconds to wait * @return icon from url * @throws IOException if timeout has been exceeded */ private static Icon readIcon( String url, int waitSecs ) throws IOException { final URL urlLoc = new URL( url ); final Icon[] icons = new Icon[ 1 ]; Thread loader = new Thread( "IconLoader " + url ) { public void run() { icons[ 0 ] = new ImageIcon( urlLoc ); } }; loader.start(); try { loader.join( waitSecs * 1000 ); Icon icon = icons[ 0 ]; if ( icon != null ) { return icon; } else { throw new IOException( "Icon load timeout (" + waitSecs + "s)" ); } } catch ( InterruptedException e ) { throw (IOException) new IOException( "Load interrupted" ) .initCause( e ); } } /** * Icon implementation which looks like an existing one, but is resized * down if necessary. */ private static class SizedIcon implements Icon { private final Icon icon_; private final int size_; private final double factor_; /** * Constructor. * * @param icon original icon * @param size number of horizontal and vertical pixels in this icon */ public SizedIcon( Icon icon, int size ) { icon_ = icon; size_ = size; factor_ = Math.min( 1.0, Math.min( size / (double) icon.getIconWidth(), size / (double) icon.getIconHeight() ) ); } public int getIconWidth() { return size_; } public int getIconHeight() { return size_; } public void paintIcon( Component c, Graphics g, int x, int y ) { int iw = icon_.getIconWidth(); int ih = icon_.getIconHeight(); if ( factor_ == 1.0 ) { icon_.paintIcon( c, g, x + ( size_ - iw ) / 2, y + ( size_ - ih ) / 2 ); } else { Graphics2D g2 = (Graphics2D) g; AffineTransform trans = g2.getTransform(); g2.translate( x + ( size_ - iw * factor_ ) / 2, y + ( size_ - ih * factor_ ) / 2 ); g2.scale( factor_, factor_ ); icon_.paintIcon( c, g2, 0, 0 ); g2.setTransform( trans ); } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/IndividualCallActionManager.java000066400000000000000000000116721356450004300301540ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.awt.event.ActionEvent; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ListModel; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.HubConnector; import org.astrogrid.samp.client.ResultHandler; /** * SendActionManager which uses the Asynchronous Call/Response delivery * pattern, but allows a "broadcast" to send different message objects * to different recipients. * Multiple targetted sends rather than an actual SAMP broadcast may be * used to achieve this. * Concrete subclasses need only implement the * {@link #createMessage(org.astrogrid.samp.Client)} method. * They may also wish to to customise the returned Send and Broadcast Action * objects (for instance give them useful names and descriptions). * * @author Mark Taylor * @since 3 Dec 2008 */ public abstract class IndividualCallActionManager extends AbstractCallActionManager { private final Component parent_; /** * Constructor. * * @param parent parent component * @param connector hub connector * @param clientListModel list model containing only those clients * which are suitable recipients; * all elements must be {@link Client}s */ public IndividualCallActionManager( Component parent, GuiHubConnector connector, ListModel clientListModel ) { super( parent, connector, clientListModel ); parent_ = parent; } protected abstract Map createMessage( Client client ) throws Exception; public Action createBroadcastAction() { return new BroadcastAction(); } /** * Action which performs "broadcasts". They may actually be multiple * targetted sends. */ private class BroadcastAction extends AbstractAction { final HubConnector connector_ = getConnector(); final ListModel clientList_ = getClientListModel(); /** * Constructor. */ BroadcastAction() { putValue( SMALL_ICON, getBroadcastIcon() ); } public void actionPerformed( ActionEvent evt ) { // Identify groups of recipients which can receive the same // Message object as each other. int nc = clientList_.getSize(); Map msgMap = new HashMap(); try { for ( int ic = 0; ic < nc; ic++ ) { Client client = (Client) clientList_.getElementAt( ic ); Map message = createMessage( client ); if ( message != null ) { Message msg = Message.asMessage( message ); msg.check(); if ( ! msgMap.containsKey( msg ) ) { msgMap.put( msg, new HashSet() ); } Collection clientSet = (Collection) msgMap.get( msg ); clientSet.add( client ); } } } catch ( Exception e ) { ErrorDialog.showError( parent_, "Send Error", "Error constructing message " + e.getMessage(), e ); return; } // Send the message to each group at a time. try { HubConnection connection = connector_.getConnection(); if ( connection != null ) { for ( Iterator it = msgMap.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); Message msg = (Message) entry.getKey(); Client[] recipients = (Client[]) ((Collection) entry.getValue()) .toArray( new Client[ 0 ] ); String tag = createTag(); ResultHandler handler = createResultHandler( connection, msg, recipients ); registerHandler( tag, recipients, handler ); for ( int ir = 0; ir < recipients.length; ir++ ) { connection.call( recipients[ ir ].getId(), tag, msg ); } } } } catch ( Exception e ) { ErrorDialog.showError( parent_, "Send Error", "Error sending message " + e.getMessage(), e ); } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/MessageTrackerHubConnector.java000066400000000000000000001032161356450004300300450ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.logging.Logger; import javax.swing.AbstractListModel; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.ListCellRenderer; import javax.swing.ListModel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.client.TrackedClientSet; /** * HubConnector implementation which provides facilities for keeping track * of incoming and outgoing messages as well as the other GUI features. * * @author Mark Taylor * @since 26 Nov 2008 */ public class MessageTrackerHubConnector extends GuiHubConnector implements ClientTransmissionHolder { private final TransmissionListModel txListModel_; private final TransmissionListModel rxListModel_; private final TransmissionTableModel txTableModel_; private final TransmissionTableModel rxTableModel_; private final Map callAllMap_; private final Map txModelMap_; private final Map rxModelMap_; private final ListDataListener transListListener_; private final int listRemoveDelay_; private static final Logger logger_ = Logger.getLogger( MessageTrackerHubConnector.class.getName() ); /** * Constructs a hub connector with default message tracker GUI expiry times. * * @param profile profile implementation */ public MessageTrackerHubConnector( ClientProfile profile ) { this( profile, 500, 20000, 100 ); } /** * Constructs a hub connector with specified message tracker GUI * expiry times. * The delay times are times in milliseconds after message resolution * before message representations expire and hence remove themselves * from gui components. * * @param profile profile implementation * @param listRemoveDelay expiry delay for summary icons in client * list display * @param tableRemoveDelay expiry delay for rows in message * table display * @param tableMaxRows maximum number of rows in message table * (beyond this limit resolved messages may be * removed early) */ public MessageTrackerHubConnector( ClientProfile profile, int listRemoveDelay, int tableRemoveDelay, int tableMaxRows ) { super( profile ); listRemoveDelay_ = listRemoveDelay; transListListener_ = new ClientTransmissionListListener(); txListModel_ = new TransmissionListModel( listRemoveDelay_ ); rxListModel_ = new TransmissionListModel( listRemoveDelay_ ); txListModel_.addListDataListener( transListListener_ ); rxListModel_.addListDataListener( transListListener_ ); txTableModel_ = new TransmissionTableModel( false, true, tableRemoveDelay, tableMaxRows ); rxTableModel_ = new TransmissionTableModel( true, false, tableRemoveDelay, tableMaxRows ); callAllMap_ = new HashMap(); // access only from EDT txModelMap_ = new WeakHashMap(); rxModelMap_ = new WeakHashMap(); } /** * Returns a ListModel representing the pending messages sent using * this connector. * Elements of the model are {@link Transmission} objects. * * @return transmission list model */ public ListModel getTxListModel() { return txListModel_; } /** * Returns a ListModel representing the pending messages received using * this connector. * Elements of the model are {@link Transmission} objects. * * @return transmission list model */ public ListModel getRxListModel() { return rxListModel_; } public ListModel getTxListModel( Client client ) { if ( ! txModelMap_.containsKey( client ) ) { TransmissionListModel listModel = new TransmissionListModel( listRemoveDelay_ ); listModel.addListDataListener( transListListener_ ); txModelMap_.put( client, listModel ); } return (ListModel) txModelMap_.get( client ); } public ListModel getRxListModel( Client client ) { if ( ! rxModelMap_.containsKey( client ) ) { TransmissionListModel listModel = new TransmissionListModel( listRemoveDelay_ ); listModel.addListDataListener( transListListener_ ); rxModelMap_.put( client, listModel ); } return (ListModel) rxModelMap_.get( client ); } /** * Returns a component which displays messages currently being * sent/received by this connector. * * @return iconSize height of icons in box */ public JComponent createMessageBox( int iconSize ) { JComponent box = createMessageBox( iconSize, rxListModel_, txListModel_ ); registerUpdater( box, ENABLE_COMPONENT ); return box; } /** * Returns a component which displays messages in receiver and/or sender * list models. * * @param iconSize height of icons * @param rxListModel list model containing received * {@link Transmission} objects * @param txListModel list model containing sent * {@link Transmission} objects */ public static JComponent createMessageBox( int iconSize, ListModel rxListModel, ListModel txListModel ) { final Color dtColor = UIManager.getColor( "Label.disabledText" ); JComponent box = new JPanel() { final Color enabledFg = getForeground(); final Color enabledBg = Color.WHITE; final Color disabledFg = null; final Color disabledBg = getBackground(); public void setEnabled( boolean enabled ) { super.setEnabled( enabled ); setForeground( enabled ? enabledFg : disabledFg ); setBackground( enabled ? enabledBg : disabledFg ); } }; box.setLayout( new BoxLayout( box, BoxLayout.X_AXIS ) ); if ( rxListModel != null ) { IconBox rxBox = new IconBox( iconSize ); rxBox.setOpaque( false ); rxBox.setTrailing( true ); rxBox.setModel( rxListModel ); rxBox.setRenderer( new TransmissionCellRenderer() { public String getToolTipText( IconBox iconBox, Object value, int index ) { if ( value instanceof Transmission ) { Transmission trans = (Transmission) value; return new StringBuffer() .append( trans.getMessage().getMType() ) .append( " <- " ) .append( trans.getSender().toString() ) .toString(); } else { return super.getToolTipText( iconBox, value, index ); } } } ); Dimension prefSize = rxBox.getPreferredSize(); prefSize.width = iconSize * 3; rxBox.setPreferredSize( prefSize ); box.add( rxBox ); } IconBox cBox = new IconBox( iconSize ); cBox.setOpaque( false ); cBox.setBorder( BorderFactory.createEmptyBorder( 0, 2, 0, 2 ) ); cBox.setModel( new AbstractListModel() { public int getSize() { return 1; } public Object getElementAt( int index ) { return "app"; } } ); cBox.setRenderer( new TransmissionCellRenderer() ); Dimension cSize = cBox.getPreferredSize(); cBox.setMaximumSize( cSize ); cBox.setMinimumSize( cSize ); box.add( cBox ); if ( txListModel != null ) { IconBox txBox = new IconBox( iconSize ); txBox.setOpaque( false ); txBox.setModel( txListModel ); txBox.setRenderer( new TransmissionCellRenderer() { public String getToolTipText( IconBox iconBox, Object value, int index ) { if ( value instanceof Transmission ) { Transmission trans = (Transmission) value; return new StringBuffer() .append( trans.getMessage().getMType() ) .append( " -> " ) .append( trans.getReceiver().toString() ) .toString(); } else { return super.getToolTipText( iconBox, value, index ); } } } ); Dimension prefSize = txBox.getPreferredSize(); prefSize.width = iconSize * 3; txBox.setPreferredSize( prefSize ); box.add( txBox ); } box.setBackground( Color.WHITE ); box.setBorder( createBoxBorder() ); return box; } public ListCellRenderer createClientListCellRenderer() { MessageTrackerListCellRenderer renderer = new MessageTrackerListCellRenderer( this ); renderer.setTransmissionCellRenderer( new TransmissionCellRenderer() { public String getToolTipText( IconBox iconBox, Object value, int index ) { return value instanceof Transmission ? ((Transmission) value).getMessage().getMType() : super.getToolTipText( iconBox, value, index ); } } ); return renderer; } public JComponent createMonitorPanel() { JTabbedPane tabber = new JTabbedPane(); // Add client view tab. HubView hubView = new HubView( false ); hubView.setClientListModel( getClientListModel() ); hubView.getClientList() .setCellRenderer( createClientListCellRenderer() ); tabber.add( "Clients", hubView ); // Add received message tab. tabber.add( "Received Messages", new TransmissionView( rxTableModel_ ) ); // Add sent message tab. tabber.add( "Sent Messages", new TransmissionView( txTableModel_ ) ); // Position and return. JComponent panel = new JPanel( new BorderLayout() ); panel.add( tabber, BorderLayout.CENTER ); return panel; } protected HubConnection createConnection() throws SampException { HubConnection connection = super.createConnection(); return connection == null ? null : new MessageTrackerHubConnection( connection ); } /** * Schedules a new transmission to add to the appropriate list models. * May be called from any thread. * * @param trans transmission * @param tx true for send, false for receive */ private void scheduleAddTransmission( final Transmission trans, final boolean tx ) { SwingUtilities.invokeLater( new Runnable() { public void run() { ( tx ? txTableModel_ : rxTableModel_ ).addTransmission( trans ); ((TransmissionListModel) getTxListModel( trans.getSender() )) .addTransmission( trans ); ((TransmissionListModel) getRxListModel( trans.getReceiver() )) .addTransmission( trans ); } } ); } /** * Schedules a response to be registered for a previously added * transmission. * May be called from any thread. * * @param trans transmission * @param response response to associated with trans */ private void scheduleSetResponse( final Transmission trans, final Response response ) { SwingUtilities.invokeLater( new Runnable() { public void run() { trans.setResponse( response ); } } ); } /** * Schedules an error to be registered for a previously added * transmission. * May be called from any thread. * * @param trans transmission * @param error exception */ private void scheduleSetFailure( final Transmission trans, final Throwable error ) { SwingUtilities.invokeLater( new Runnable() { public void run() { trans.setError( error ); } } ); } /** * HubConnection object which intercepts calls to keep track of * outgoing and incoming messages. */ private class MessageTrackerHubConnection extends WrapperHubConnection { private Client selfClient_; /** * Constructor. * * @param base connection on which this one is based */ MessageTrackerHubConnection( HubConnection base ) { super( base ); } /** * Returns a Client object for use in Transmission objects * which represents this connection's owner. * This has to be the same object as is used in the client set, * otherwise the various models don't get updated correctly. * For this reason, it has to be obtained lazily, after the client set * has been initialised. * * @return self client object */ Client getSelfClient() { if ( selfClient_ == null ) { selfClient_ = (Client) getClientMap().get( getRegInfo().getSelfId() ); assert selfClient_ != null; txModelMap_.put( selfClient_, txListModel_ ); rxModelMap_.put( selfClient_, rxListModel_ ); } return selfClient_; } public void notify( final String recipientId, final Map msg ) throws SampException { // Construct a transmission corresponding to this notify and // add it to the send list. Client recipient = (Client) getClientMap().get( recipientId ); Transmission trans = recipient == null ? null : new Transmission( getSelfClient(), recipient, Message.asMessage( msg ), null, null ); if ( trans != null ) { scheduleAddTransmission( trans, true ); } // Do the actual send. try { super.notify( recipientId, msg ); // Notify won't generate a response, so signal that now. if ( trans != null ) { scheduleSetResponse( trans, null ); } } // If the send failed, signal it. catch ( SampException e ) { if ( trans != null ) { scheduleSetFailure( trans, e ); } throw e; } } public List notifyAll( Map msg ) throws SampException { // Do the send. List recipientIdList = super.notifyAll( msg ); // Construct a list of transmissions corresponding to this notify // and add them to the send list. final List transList = new ArrayList(); Message message = Message.asMessage( msg ); Client sender = getSelfClient(); for ( Iterator it = recipientIdList.iterator(); it.hasNext(); ) { Client recipient = (Client) getClientMap().get( (String) it.next() ); if ( recipient != null ) { Transmission trans = new Transmission( sender, recipient, message, null, null ); scheduleAddTransmission( trans, true ); // Notify won't generate a response, so signal that now. scheduleSetResponse( trans, null ); } } return recipientIdList; } public String call( String recipientId, String msgTag, Map msg ) throws SampException { // Construct a transmission corresponding to this call // and add it to the send list. Client recipient = (Client) getClientMap().get( recipientId ); Transmission trans = recipient == null ? null : new Transmission( getSelfClient(), recipient, Message.asMessage( msg ), msgTag, null ); if ( trans != null ) { scheduleAddTransmission( trans, true ); } // Do the actual call. try { return super.call( recipientId, msgTag, msg ); } // If the send failed, signal that since no reply will be // forthcoming. catch ( final SampException e ) { scheduleSetFailure( trans, e ); throw e; } } public Map callAll( final String msgTag, Map msg ) throws SampException { // This is a bit more complicated than the other cases. // We can't construct the list of transmissions before the send, // since we don't know which are the recipient clients. // But if we wait until after the delegated callAll() method // we may miss some early responses to it. So we have to // put in place a mechanism for dealing with responses before // we know exactly what they are responses to. // Prepare and store a CallAllHandler for this. final CallAllHandler cah = new CallAllHandler( msgTag ); SwingUtilities.invokeLater( new Runnable() { public void run() { callAllMap_.put( msgTag, cah ); } } ); // Do the actual call. Map callMap = super.callAll( msgTag, msg ); // Prepare a post-facto list of the transmissions which were sent. List transList = new ArrayList(); Message message = Message.asMessage( msg ); for ( Iterator it = callMap.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); String recipientId = (String) entry.getKey(); Client sender = getSelfClient(); Client recipient = (Client) getClientMap().get( recipientId ); if ( recipient != null ) { String msgId = (String) entry.getValue(); Transmission trans = new Transmission( sender, recipient, message, msgTag, msgId ); scheduleAddTransmission( trans, true ); transList.add( trans ); } } final Transmission[] transmissions = (Transmission[]) transList.toArray( new Transmission[ 0 ] ); // And inform the CallAllHandler what the transmissions were, so // it knows how to process (possibly already received) responses. SwingUtilities.invokeLater( new Runnable() { public void run() { cah.setTransmissions( transmissions ); } } ); return callMap; } public Response callAndWait( String recipientId, Map msg, int timeout ) throws SampException { // Construct a transmission obejct corresponding to this call // and add it to the send list. Client recipient = (Client) getClientMap().get( recipientId ); Transmission trans = recipient == null ? null : new Transmission( getSelfClient(), recipient, Message.asMessage( msg ), "", "" ); if ( trans != null ) { scheduleAddTransmission( trans, true ); } // Do the actual call. try { Response response = super.callAndWait( recipientId, msg, timeout ); // Inform the transmission of the response. if ( trans != null ) { scheduleSetResponse( trans, response ); } return response; } // In case of error, inform the transmission of failure. catch ( SampException e ) { if ( trans != null ) { scheduleSetFailure( trans, e ); } throw e; } } public void reply( final String msgId, final Map response ) throws SampException { // Do the actual reply. Throwable err; try { super.reply( msgId, response ); err = null; } catch ( Throwable e ) { err = e; } final Throwable error = err; // Inform the existing transmission on the receive list // that the reply has been made. SwingUtilities.invokeLater( new Runnable() { public void run() { int nt = rxListModel_.getSize(); for ( int i = 0; i < nt; i++ ) { Transmission trans = (Transmission) rxListModel_.getElementAt( i ); if ( msgId.equals( trans.getMessageId() ) ) { trans.setResponse( Response .asResponse( response ) ); if ( error != null ) { trans.setError( error ); } return; } } logger_.warning( "Orphan reply " + msgId + " - replier programming error?" ); } } ); } public void setCallable( CallableClient callable ) throws SampException { // Install a wrapper-like callable client which can intercept // the calls to keep track of send/received messages. CallableClient mtCallable = new MessageTrackerCallableClient( callable, this ); super.setCallable( mtCallable ); } } /** * CallableClient wrapper class which intercepts calls to keep track * of sent and received messages. */ private class MessageTrackerCallableClient implements CallableClient { private final CallableClient base_; private final MessageTrackerHubConnection connection_; /** * Constructor. * * @param base base callable * @param connection hub connection */ MessageTrackerCallableClient( CallableClient base, MessageTrackerHubConnection connection ) { base_ = base; connection_ = connection; } /** * Returns a Client object for use in Transmission objects * which represents this connection's owner. * * @return self client object */ private Client getSelfClient() { return connection_.getSelfClient(); } public void receiveCall( String senderId, String msgId, Message msg ) throws Exception { // Construct a transmission corresponding to the incoming call // and add it to the receive list. Client sender = (Client) getClientMap().get( senderId ); Transmission trans = sender == null ? null : new Transmission( sender, getSelfClient(), msg, null, msgId ); if ( trans != null ) { scheduleAddTransmission( trans, false ); } // Actually handle the call. try { base_.receiveCall( senderId, msgId, msg ); } // If the call handler fails, inform the transmission. catch ( Exception e ) { scheduleSetFailure( trans, e ); throw e; } } public void receiveNotification( String senderId, Message msg ) throws Exception { Client sender = (Client) getClientMap().get( senderId ); // Actually handle the notification. base_.receiveNotification( senderId, msg ); // Construct a transmission corresponding to the incoming // notification and add it to the receive list. // Give it a null response immediately, since being a notify // it won't get another one. if ( sender != null ) { final Transmission trans = new Transmission( sender, getSelfClient(), msg, null, null ); scheduleAddTransmission( trans, false ); scheduleSetResponse( trans, null ); } } public void receiveResponse( final String responderId, final String msgTag, final Response response ) throws Exception { // Actually handle the response. base_.receiveResponse( responderId, msgTag, response ); // Update state of the send list. // This isn't foolproof - if a sender has re-used the same msgTag // for a call and a callAll this handling might get confused - // but then so would the sender. SwingUtilities.invokeLater( new Runnable() { public void run() { // If the message was sent using callAll, handle using // the registered CallAllHandler. CallAllHandler cah = (CallAllHandler) callAllMap_.get( msgTag ); if ( cah != null ) { cah.addResponse( responderId, response ); } // Otherwise find the relevant Transmission in the // send list and inform it of the response. else { int nt = txListModel_.getSize(); for ( int i = 0; i < nt; i++ ) { Transmission trans = (Transmission) txListModel_.getElementAt( i ); if ( responderId.equals( trans.getReceiver() .getId() ) && msgTag.equals( trans.getMessageTag() ) ) { trans.setResponse( response ); return; } } logger_.warning( "Orphan reply " + msgTag + " - possible hub error?" ); } } } ); } } /** * Class used to keep track of outgoing callAll() messages. * It needs to be able to match Responses with Transmissions, * but the complication is that a Response may arrive either before * or after its corresponding Transmission is known. */ private class CallAllHandler { private final String msgTag_; private final Map responseMap_; private Collection transSet_; /** * Constructor. * * @param msgTag message tag labelling the callAll send */ CallAllHandler( String msgTag ) { msgTag_ = msgTag; responseMap_ = new HashMap(); } /** * Called once when the list of transmissions corresponding to the * callAll invocation is known. * * @param transmissions list of transmission objects, one for each * callAll recipient */ public void setTransmissions( Transmission[] transmissions ) { // Store transmissions for later. if ( transSet_ != null ) { throw new IllegalStateException(); } transSet_ = new HashSet( Arrays.asList( transmissions ) ); // Process any responses already in. for ( Iterator it = responseMap_.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); String responderId = (String) entry.getKey(); Response response = (Response) entry.getValue(); processResponse( responderId, response ); } retireIfDone(); } /** * Supplies a response to the callAll invocation handled by this object. * * @param responderId client ID of responder * @param response response */ public void addResponse( String responderId, Response response ) { // If we know what transmissions have been sent, we can process // this response directly. if ( transSet_ != null ) { processResponse( responderId, response ); retireIfDone(); } // Otherwise store the response and defer processing until we do. else { responseMap_.put( responderId, response ); } } /** * Does the work of passing a received response to the relevant * member of the transmission list. * May only be called following {@link #setTransmissions}. * * @param responderId client ID of responder * @param response response */ private void processResponse( String responderId, Response response ) { assert transSet_ != null; for ( Iterator it = transSet_.iterator(); it.hasNext(); ) { Transmission trans = (Transmission) it.next(); if ( trans.getReceiver().getId().equals( responderId ) ) { trans.setResponse( response ); it.remove(); return; } } logger_.warning( "Orphan reply " + msgTag_ + " - possible hub error?" ); } /** * Checks whether this object has any further work to do * (any more responses are expected) and if not uninstalls itself, * at which point it becomes unreachable and can be garbage collected. * May only be called following {@link #setTransmissions}. */ private void retireIfDone() { assert transSet_ != null; if ( transSet_.isEmpty() ) { assert callAllMap_.containsKey( msgTag_ ); callAllMap_.remove( msgTag_ ); } } } /** * ListDataListener implementation which responds to transmission list * events and passes them on to the client list, since any changes to * the transmission list may change the way that a client is renderered * in the JList. */ private class ClientTransmissionListListener implements ListDataListener { public void contentsChanged( ListDataEvent evt ) { transmissionChanged( evt ); } public void intervalAdded( ListDataEvent evt ) { transmissionChanged( evt ); } public void intervalRemoved( ListDataEvent evt ) { transmissionChanged( evt ); } /** * Called when there is any change to a known transmission. */ private void transmissionChanged( ListDataEvent evt ) { Object src = evt.getSource(); assert src instanceof Transmission; if ( src instanceof Transmission ) { Transmission trans = (Transmission) src; TrackedClientSet clientSet = getClientSet(); clientSet.updateClient( trans.getReceiver(), false, false ); clientSet.updateClient( trans.getSender(), false, false ); } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/MessageTrackerHubService.java000066400000000000000000000427151356450004300275210ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import javax.swing.ImageIcon; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.hub.ClientSet; import org.astrogrid.samp.hub.HubClient; import org.astrogrid.samp.hub.ProfileToken; /** * GuiHubService subclass which additionally keeps track of which messages * have been sent and received, and can provide a graphical display of these. * The overhead in maintaining the GUI display can be significant if there is * high volume of message traffic. * * @author Mark Taylor * @since 20 Nov 2008 */ public class MessageTrackerHubService extends GuiHubService implements ClientTransmissionHolder { private final CallMap callMap_; private final TransmissionTableModel transTableModel_; private final int listRemoveDelay_; private final int tableRemoveDelay_; private final int tableMaxRows_; private MessageTrackerClientSet clientSet_; private ListSelectionModel selectionModel_; /** * Constructs a hub service with default message tracker GUI expiry times. * * @param random random number generator */ public MessageTrackerHubService( Random random ) { this( random, 0, 20000, 100 ); } /** * Constructs a hub service with specified message tracker GUI expiry times. * The delay times are times in milliseconds after message resolution * before message representations expire and hence remove themselves * from gui components. * * @param random random number generator * @param listRemoveDelay expiry delay for summary icons in client * list display * @param tableRemoveDelay expiry delay for rows in message * table display * @param tableMaxRows maximum number of rows in message table * (beyond this limit resolved messages may be * removed early) */ public MessageTrackerHubService( Random random, int listRemoveDelay, int tableRemoveDelay, int tableMaxRows ) { super( random ); listRemoveDelay_ = listRemoveDelay; tableRemoveDelay_ = tableRemoveDelay; tableMaxRows_ = tableMaxRows; callMap_ = new CallMap(); // access only from EDT; transTableModel_ = new TransmissionTableModel( true, true, tableRemoveDelay_, tableMaxRows_ ); } public void start() { super.start(); clientSet_ = (MessageTrackerClientSet) getClientSet(); } public ClientSet createClientSet() { return new MessageTrackerClientSet( getIdComparator() ); } public HubClient createClient( String publicId, ProfileToken ptoken ) { return new MessageTrackerHubClient( publicId, ptoken ); } public JComponent createHubPanel() { JTabbedPane tabber = new JTabbedPane(); // Add client view tab. HubView hubView = new HubView( true ); hubView.setClientListModel( getClientListModel() ); JList jlist = hubView.getClientList(); jlist.setCellRenderer( new MessageTrackerListCellRenderer( this ) ); jlist.addMouseListener( new HubClientPopupListener( this ) ); selectionModel_ = jlist.getSelectionModel(); tabber.add( "Clients", hubView ); // Add messages tab. tabber.add( "Messages", new TransmissionView( transTableModel_ ) ); // Position and return. JComponent panel = new JPanel( new BorderLayout() ); panel.add( tabber, BorderLayout.CENTER ); return panel; } public ListSelectionModel getClientSelectionModel() { return selectionModel_; } /** * Returns a ListModel representing the pending messages sent * from a given client. * Elements of the model are {@link Transmission} objects. * * @param client client owned by this hub service * @return transmission list model */ public ListModel getTxListModel( Client client ) { return client instanceof MessageTrackerHubClient ? ((MessageTrackerHubClient) client).txListModel_ : null; } /** * Returns a ListModel representing the pending messages received * by a given client. * Elements of the model are {@link Transmission} objects. * * @param client client owned by this hub service * @return transmission list model */ public ListModel getRxListModel( Client client ) { return client instanceof MessageTrackerHubClient ? ((MessageTrackerHubClient) client).rxListModel_ : null; } protected void reply( HubClient caller, String msgId, final Map response ) throws SampException { // Notify the transmission object corresponding to this response // that the response has been received. final Object callKey = getCallKey( caller, msgId ); SwingUtilities.invokeLater( new Runnable() { public void run() { Transmission trans = callMap_.remove( callKey ); if ( trans != null ) { trans.setResponse( Response.asResponse( response ) ); } } } ); // Forward the call to the base implementation. super.reply( caller, msgId, response ); } /** * Registers a newly created transmission with internal data models * as required. * * @param trans new transmission to track */ private void addTransmission( Transmission trans ) { ((MessageTrackerHubClient) trans.getSender()) .txListModel_.addTransmission( trans ); ((MessageTrackerHubClient) trans.getReceiver()) .rxListModel_.addTransmission( trans ); transTableModel_.addTransmission( trans ); } /** * Returns a key for use in the call map. * Identifies a call/response mode transmission. * * @param receiver message receiver * @param msgId message ID */ private static Object getCallKey( Client receiver, String msgId ) { return new StringBuffer() .append( msgId ) .append( "->" ) .append( receiver.getId() ) .toString(); } /** * HubClient class used by this HubService implementation. */ private class MessageTrackerHubClient extends HubClient { final TransmissionListModel rxListModel_; final TransmissionListModel txListModel_; /** * Constructor. * * @param publicId public ID * @param ptoken connection source */ public MessageTrackerHubClient( String publicId, ProfileToken ptoken ) { super( publicId, ptoken ); // Prepare list models for the transmissions sent/received by // a given client. These models are updated as the hub forwards // messages and responses. The contents of these models are // Transmission objects. txListModel_ = new TransmissionListModel( listRemoveDelay_ ); rxListModel_ = new TransmissionListModel( listRemoveDelay_ ); } public void setCallable( CallableClient callable ) { super.setCallable( callable == null ? null : new MessageTrackerCallableClient( callable, this ) ); } } /** * Wrapper implementation for the CallableClient class which intercepts * calls to update sent and received transmission list models. */ private class MessageTrackerCallableClient implements CallableClient { private final CallableClient base_; private final MessageTrackerHubClient client_; /** * Constructor. * * @param base callable on which this one is based * @param client hub client for which this receiver is operating */ MessageTrackerCallableClient( CallableClient base, MessageTrackerHubClient client ) { base_ = base; client_ = client; } public void receiveCall( String senderId, String msgId, Message msg ) throws SampException { // When a call is received, create a corresponding Transmission // object and add it to both the send list of the sender and // the receive list of the recipient. MessageTrackerHubClient sender = (MessageTrackerHubClient) clientSet_.getFromPublicId( senderId ); MessageTrackerHubClient recipient = client_; final Transmission trans = new Transmission( sender, recipient, msg, null, msgId ); final Object callKey = getCallKey( recipient, msgId ); SwingUtilities.invokeLater( new Runnable() { public void run() { callMap_.add( callKey, trans ); addTransmission( trans ); } } ); // Forward the call to the base implementation. try { base_.receiveCall( senderId, msgId, msg ); } catch ( final Exception e ) { SwingUtilities.invokeLater( new Runnable() { public void run() { trans.setError( e ); } } ); } } public void receiveNotification( String senderId, Message msg ) throws SampException { // When a notification is received, create a corresponding // Transmission object and add it to both the send list of the // sender and the receive list of the recipient. MessageTrackerHubClient sender = (MessageTrackerHubClient) clientSet_.getFromPublicId( senderId ); MessageTrackerHubClient recipient = client_; final Transmission trans = new Transmission( sender, recipient, msg, null, null ); SwingUtilities.invokeLater( new Runnable() { public void run() { addTransmission( trans ); } } ); // Forward the call to the base implementation. Exception error; try { base_.receiveNotification( senderId, msg ); error = null; } catch ( Exception e ) { error = e; } // Since it's a notify, no response will be forthcoming. // So signal a no-response (or send failure) directly. final Throwable err2 = error; SwingUtilities.invokeLater( new Runnable() { public void run() { if ( err2 == null ) { trans.setResponse( null ); } else { trans.setError( err2 ); } } } ); } public void receiveResponse( String responderId, String msgTag, Response response ) throws Exception { // Just forward the call to the base implementation. // Handling the responses happens elsewhere (where we have the // msgId not the msgTag). base_.receiveResponse( responderId, msgTag, response ); } } /** * ClientSet implementation used by this hub service. */ private class MessageTrackerClientSet extends GuiClientSet { private final ListDataListener transListener_; /** * Constructor. * * @param clientIdComparator comparator for client IDs */ MessageTrackerClientSet( Comparator clientIdComparator ) { super( clientIdComparator ); // Prepare a listener which will be notified when a client's // send or receive list changes, or when any of the Transmission // objects in those lists changes state. transListener_ = new ListDataListener() { public void contentsChanged( ListDataEvent evt ) { transmissionChanged( evt ); } public void intervalAdded( ListDataEvent evt ) { transmissionChanged( evt ); } public void intervalRemoved( ListDataEvent evt ) { transmissionChanged( evt ); } private void transmissionChanged( ListDataEvent evt ) { Object src = evt.getSource(); assert src instanceof Transmission; if ( src instanceof Transmission ) { Transmission trans = (Transmission) src; int nclient = getSize(); for ( int ic = 0; ic < nclient; ic++ ) { Client client = (Client) getElementAt( ic ); if ( trans.getSender().equals( client ) || trans.getReceiver().equals( client ) ) { ListDataEvent clientEvt = new ListDataEvent( trans, ListDataEvent .CONTENTS_CHANGED, ic, ic ); fireListDataEvent( clientEvt ); } } } } }; } public void add( HubClient client ) { MessageTrackerHubClient mtClient = (MessageTrackerHubClient) client; final ListModel txListModel = mtClient.txListModel_; final ListModel rxListModel = mtClient.rxListModel_; SwingUtilities.invokeLater( new Runnable() { public void run() { txListModel.addListDataListener( transListener_ ); rxListModel.addListDataListener( transListener_ ); } } ); super.add( client ); } public void remove( HubClient client ) { super.remove( client ); MessageTrackerHubClient mtClient = (MessageTrackerHubClient) client; final TransmissionListModel txListModel = mtClient.txListModel_; final TransmissionListModel rxListModel = mtClient.rxListModel_; SwingUtilities.invokeLater( new Runnable() { public void run() { for ( int i = 0; i < txListModel.getSize(); i++ ) { ((Transmission) txListModel.getElementAt( i )) .setSenderUnregistered(); } for ( int i = 0; i < rxListModel.getSize(); i++ ) { ((Transmission) rxListModel.getElementAt( i )) .setReceiverUnregistered(); } txListModel.removeListDataListener( transListener_ ); rxListModel.removeListDataListener( transListener_ ); } } ); } } /** * Keeps track of transmissions by key. * It works somewhat like a Map, but with the difference that multiple * values may be stored under a single key. */ private static class CallMap { private final Map map_ = new HashMap(); /** * Adds a new entry. * * @param key key * @param trans value */ public void add( Object key, Transmission trans ) { assert SwingUtilities.isEventDispatchThread(); if ( ! map_.containsKey( key ) ) { map_.put( key, new ArrayList( 1 ) ); } ((List) map_.get( key )).add( trans ); } /** * Reads and removes an entry. If multiple values are stored under * the given key, one of them (the first to have been stored) * is returned, and any others are unaffected. * * @param key key * @returh a value correspondig to key */ public Transmission remove( Object key ) { assert SwingUtilities.isEventDispatchThread(); List transList = (List) map_.get( key ); if ( transList == null ) { return null; } else { assert ! transList.isEmpty(); Transmission trans = (Transmission) transList.remove( 0 ); if ( transList.isEmpty() ) { map_.remove( key ); } return trans; } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/MessageTrackerListCellRenderer.java000066400000000000000000000256421356450004300306640ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Point; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.ListModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import org.astrogrid.samp.Client; /** * ListCellRenderer which draws a representation of incoming and outgoing * messages alongside the default representation. * * @author Mark Taylor * @since 26 Nov 2008 */ class MessageTrackerListCellRenderer extends ClientListCellRenderer { private final ClientTransmissionHolder transHolder_; private final int msgGap_; private final IconBox iconBox_; private final IconListModel iconListModel_; private final Object separator_; /** * Constructor. * * @param transHolder obtains list models containing sent/received * messages */ public MessageTrackerListCellRenderer( ClientTransmissionHolder transHolder ) { transHolder_ = transHolder; iconListModel_ = new IconListModel(); msgGap_ = 10; separator_ = new Object(); iconBox_ = new IconBox( 16 ); iconBox_.setOpaque( false ); iconBox_.setBorder( BorderFactory.createEmptyBorder( 1, 1, 1, 1 ) ); iconBox_.setModel( iconListModel_ ); iconBox_.setRenderer( new TransmissionCellRenderer() { public String getToolTipText( IconBox iconBox, Object value, int index ) { if ( value instanceof Transmission ) { Transmission trans = (Transmission) value; String mtype = trans.getMessage().getMType(); Client client = iconListModel_.client_; if ( client == trans.getSender() ) { return mtype + " -> " + trans.getReceiver(); } else if ( client == trans.getReceiver() ) { return mtype + " <- " + trans.getSender(); } else { assert false; return null; } } else { return null; } } } ); } /** * Sets the cell renderer for transmission objects. * * @param transRend renderer */ public void setTransmissionCellRenderer( IconBox.CellRenderer transRend ) { iconBox_.setRenderer( transRend ); } /** * Returns the cell renderer for transmission objects. * * @return renderer */ public IconBox.CellRenderer getTransmissionCellRenderer() { return iconBox_.getRenderer(); } protected void paintComponent( Graphics g ) { super.paintComponent( g ); if ( iconListModel_.client_ != null ) { Point p = getIconBoxPosition(); Dimension boxSize = iconBox_.getPreferredSize(); if ( g.hitClip( p.x, p.y, boxSize.width, boxSize.height ) ) { g.translate( p.x, p.y ); iconBox_.paintComponent( g ); g.translate( -p.x, -p.y ); } } } public Dimension getPreferredSize() { Dimension prefSize = super.getPreferredSize(); if ( iconListModel_.client_ != null ) { Dimension boxSize = iconBox_.getPreferredSize(); prefSize.width += iconBox_.getPreferredSize().width + msgGap_; } return prefSize; } public String getToolTipText( MouseEvent evt ) { Point boxPos = getIconBoxPosition(); evt.translatePoint( -boxPos.x, -boxPos.y ); return iconBox_.getToolTipText( evt ); } public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSel, boolean hasFocus ) { Component c = super.getListCellRendererComponent( list, value, index, isSel, hasFocus ); if ( value instanceof Client ) { iconListModel_.setClient( (Client) value ); int size = c.getPreferredSize().height; if ( c instanceof JComponent ) { Insets cInsets = ((JComponent) c).getInsets(); size -= cInsets.top + cInsets.bottom; Insets bInsets = iconBox_.getInsets(); size -= bInsets.top + bInsets.bottom; } iconBox_.setTransverseSize( size ); } else { iconListModel_.setClient( null ); } return c; } /** * Returns the position at which the transmission list icon should * be drawn. * * @return icon base position */ private Point getIconBoxPosition() { Insets insets = getInsets(); return new Point( insets.left + super.getPreferredSize().width + msgGap_, insets.top ); } /** * ListModel which can be used in the icon box. * It contains entries for each received and sent message, as well as * one which notionally represents the application (visual sugar). * It is basically a combination of the rx and tx models. */ private class IconListModel implements ListModel { Client client_; ListModel rxModel_; ListModel txModel_; private final ListDataListener rxListener_; private final ListDataListener txListener_; private final List listenerList_; /** * Constructor. */ IconListModel() { listenerList_ = new ArrayList(); rxListener_ = new ListDataForwarder() { public int getOffset() { return 0; } }; txListener_ = new ListDataForwarder() { public int getOffset() { return ( rxModel_ == null ? 0 : rxModel_.getSize() ) + 1; } }; } /** * Sets the client whose transmissions this list will represent. * May be null. * * @param client client */ public void setClient( Client client ) { if ( rxModel_ != null ) { rxModel_.removeListDataListener( rxListener_ ); } if ( txModel_ != null ) { txModel_.removeListDataListener( txListener_ ); } client_ = client; rxModel_ = transHolder_.getRxListModel( client ); txModel_ = transHolder_.getTxListModel( client ); if ( rxModel_ != null ) { rxModel_.addListDataListener( rxListener_ ); } if ( txModel_ != null ) { txModel_.addListDataListener( txListener_ ); } fireEvent( new ListDataEvent( this, ListDataEvent.CONTENTS_CHANGED, -1, -1 ) ); } public int getSize() { return ( rxModel_ == null ? 0 : rxModel_.getSize() ) + 1 + ( txModel_ == null ? 0 : txModel_.getSize() ); } public Object getElementAt( int index ) { int rxSize = rxModel_ == null ? 0 : rxModel_.getSize(); if ( index < rxSize ) { return rxModel_.getElementAt( index ); } index -= rxSize; if ( index < 1 ) { return separator_; } index -= 1; int txSize = txModel_ == null ? 0 : txModel_.getSize(); if ( index < txSize ) { return txModel_.getElementAt( index ); } index -= txSize; throw new IllegalArgumentException(); } public void addListDataListener( ListDataListener listener ) { listenerList_.add( listener ); } public void removeListDataListener( ListDataListener listener ) { listenerList_.remove( listener ); } /** * Passes an event on to registered ListDataListeners. */ private void fireEvent( ListDataEvent evt ) { for ( Iterator it = listenerList_.iterator(); it.hasNext(); ) { ListDataListener listener = (ListDataListener) it.next(); switch ( evt.getType() ) { case ListDataEvent.INTERVAL_ADDED: listener.intervalAdded( evt ); break; case ListDataEvent.INTERVAL_REMOVED: listener.intervalRemoved( evt ); break; case ListDataEvent.CONTENTS_CHANGED: listener.contentsChanged( evt ); break; default: assert false; } } } /** * Listener implementation which can listen to constituent (rx and tx) * models and forward events from them to listeners to this model. */ private abstract class ListDataForwarder implements ListDataListener { /** * Returns the offset into the IconBoxModel at which the * model this listener is listening to starts. * * @return model element offset */ abstract int getOffset(); public void intervalAdded( ListDataEvent evt ) { forwardEvent( evt ); } public void intervalRemoved( ListDataEvent evt ) { forwardEvent( evt ); } public void contentsChanged( ListDataEvent evt ) { forwardEvent( evt ); } /** * Takes an event received by this listener, adjusts its * indexes appropriately, and forwards it to listeners to this * model. * * @param evt event to forward */ private void forwardEvent( ListDataEvent evt ) { Object src = evt.getSource(); int i0 = evt.getIndex0(); int i1 = evt.getIndex1(); if ( 0 <= i0 && i0 <= i1 ) { int offset = getOffset(); fireEvent( new ListDataEvent( evt.getSource(), evt.getType(), evt.getIndex0() + offset, evt.getIndex1() + offset ) ); } else { fireEvent( evt ); } } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/NotifyActionManager.java000066400000000000000000000157251356450004300265430ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JMenu; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.HubConnector; /** * SendActionManager subclass which works with messages of a single MType, * using the Notify delivery pattern. * * @author Mark Taylor * @since 5 Sep 2008 */ public abstract class NotifyActionManager extends SendActionManager { private final Component parent_; private final GuiHubConnector connector_; private final String sendType_; private static final Logger logger_ = Logger.getLogger( NotifyActionManager.class.getName() ); /** * Constructor. * * @param parent parent component * @param connector hub connector * @param mtype MType for messages transmitted by this object's actions * @param sendType short string identifying the kind of thing being * sent (used for action descriptions etc) */ public NotifyActionManager( Component parent, GuiHubConnector connector, String mtype, String sendType ) { super( connector, new SubscribedClientListModel( connector, mtype ) ); parent_ = parent; connector_ = connector; sendType_ = sendType; updateState(); } /** * Generates the message which is sent to one or all clients * by this object's actions. * * @return {@link org.astrogrid.samp.Message}-like Map representing * message to transmit */ protected abstract Map createMessage() throws Exception; /** * Called when a message has been sent by this object. * The default action is to notify via the logging system. * Subclasses may override this method. * * @param connection connection object * @param msg the message which was sent * @param recipients the recipients to whom an attempt was made to send * the message */ protected void messageSent( HubConnection connection, Message msg, Client[] recipients ) { for ( int i = 0; i < recipients.length; i++ ) { logger_.info( "Message " + msg.getMType() + " sent to " + recipients[ i ] ); } } protected Action createBroadcastAction() { Action act = new AbstractAction() { public void actionPerformed( ActionEvent evt ) { List recipientIdList = null; Message msg = null; HubConnection connection = null; try { msg = Message.asMessage( createMessage() ); msg.check(); connection = connector_.getConnection(); if ( connection != null ) { recipientIdList = connection.notifyAll( msg ); } } catch ( Exception e ) { ErrorDialog.showError( parent_, "Send Error", "Send failure " + e.getMessage(), e ); } if ( recipientIdList != null ) { assert connection != null; assert msg != null; List recipientList = new ArrayList(); Map clientMap = connector_.getClientMap(); for ( Iterator it = recipientIdList.iterator(); it.hasNext(); ) { String id = (String) it.next(); Client recipient = (Client) clientMap.get( id ); if ( recipient != null ) { recipientList.add( recipient ); } } messageSent( connection, msg, (Client[]) recipientList.toArray( new Client[ 0 ] ) ); } } }; act.putValue( Action.NAME, "Broadcast " + sendType_ ); act.putValue( Action.SHORT_DESCRIPTION, "Transmit " + sendType_ + " to all applications" + " listening using the SAMP protocol" ); act.putValue( Action.SMALL_ICON, getBroadcastIcon() ); return act; } /** * Returns a new menu for targetted sends with a title suitable for * this object. * * @return new send menu */ public JMenu createSendMenu() { JMenu menu = super.createSendMenu( "Send " + sendType_ + " to..." ); menu.setIcon( getSendIcon() ); return menu; } protected Action getSendAction( Client client ) { return new SendAction( client ); } /** * Action which performs a send. */ private class SendAction extends AbstractAction { private final Client client_; private final String cName_; /** * Constructor. * * @param client target client */ SendAction( Client client ) { client_ = client; cName_ = client.toString(); putValue( NAME, cName_ ); putValue( SHORT_DESCRIPTION, "Transmit " + sendType_ + " to " + cName_ + " using SAMP protocol" ); } public void actionPerformed( ActionEvent evt ) { boolean sent = false; Message msg = null; HubConnection connection = null; try { msg = Message.asMessage( createMessage() ); msg.check(); connection = connector_.getConnection(); if ( connection != null ) { connection.notify( client_.getId(), msg ); sent = true; } } catch ( Exception e ) { ErrorDialog.showError( parent_, "Send Error", "Send failure " + e.getMessage(), e ); } if ( sent ) { assert connection != null; assert msg != null; messageSent( connection, msg, new Client[] { client_ } ); } } public boolean equals( Object o ) { if ( o instanceof SendAction ) { SendAction other = (SendAction) o; return this.client_.equals( other.client_ ) && this.cName_.equals( other.cName_ ); } else { return false; } } public int hashCode() { return client_.hashCode() * 23 + cName_.hashCode(); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/PopupResultHandler.java000066400000000000000000000330571356450004300264400ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.Timer; import org.astrogrid.samp.Client; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ResultHandler; /** * ResultHandler which pops up a window displaying progress and success * of a message sent to one or many recipients. * * @author Mark Taylor * @since 12 Nov 2008 */ public class PopupResultHandler extends JFrame implements ResultHandler { private final Map clientMap_; private final int closeDelayMillis_; private final JCheckBox autoCloser_; /** * Constructor. * * @param parent parent component, used for window placement; * may be null * @param title window title * @param msg message which was sent * @param recipients clients from which responses are expected * @param closeDelay number of seconds after final response received * that window closes automatically; if <0, no auto close */ public PopupResultHandler( Component parent, String title, final Message msg, Client[] recipients, int closeDelay ) { super( title ); closeDelayMillis_ = closeDelay * 1000; // Set up manual window close action. Action closeAction = new AbstractAction( "Close" ) { public void actionPerformed( ActionEvent evt ) { PopupResultHandler.this.dispose(); } }; // Set up check box for configuring auto close autoCloser_ = closeDelayMillis_ >= 0 ? new JCheckBox( "Auto Close", true ) : null; // Set up per-client response handler objects clientMap_ = new HashMap(); for ( int i = 0; i < recipients.length; i++ ) { Client client = recipients[ i ]; clientMap_.put( client, new ClientHandler( client ) ); } // Prepare component layout. Container content = getContentPane(); content.setLayout( new BorderLayout() ); JComponent main = new JPanel( new BorderLayout() ); content.add( main ); // Panel containing control buttons (window close) Box buttonBox = Box.createHorizontalBox(); buttonBox.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); if ( closeDelayMillis_ >= 0 ) { buttonBox.add( Box.createHorizontalGlue() ); buttonBox.add( autoCloser_ ); } buttonBox.add( Box.createHorizontalGlue() ); buttonBox.add( new JButton( closeAction ) ); buttonBox.add( Box.createHorizontalGlue() ); main.add( buttonBox, BorderLayout.SOUTH ); // Panel containing information about sent message. Box msgBox = Box.createVerticalBox(); msgBox.setBorder( ClientPanel.createTitledBorder( "Message" ) ); Box mtypeLine = Box.createHorizontalBox(); mtypeLine.add( new JLabel( msg.getMType() ) ); mtypeLine.add( Box.createHorizontalStrut( 10 ) ); mtypeLine.add( Box.createHorizontalGlue() ); Action msgDetailAction = new DetailAction( "Detail", "Show details of message sent", "Message Sent", "SAMP message sent:" ) { protected Map getSampMap() { return msg; } }; mtypeLine.add( new JButton( msgDetailAction ) ); msgBox.add( mtypeLine ); main.add( msgBox, BorderLayout.NORTH ); // Panel containing per-client response information. GridBagLayout layer = new GridBagLayout(); GridBagConstraints cons = new GridBagConstraints(); cons.weighty = 1; cons.gridx = 0; cons.gridy = 0; cons.insets = new Insets( 0, 2, 0, 2 ); JComponent resultBox = new JPanel( layer ); resultBox.setBorder( ClientPanel.createTitledBorder( "Responses" ) ); for ( Iterator it = clientMap_.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); ClientHandler ch = (ClientHandler) entry.getValue(); // Client name. cons.anchor = GridBagConstraints.WEST; cons.weightx = 0; cons.fill = GridBagConstraints.NONE; Component nameLabel = ch.nameLabel_; layer.setConstraints( nameLabel, cons ); resultBox.add( nameLabel ); // Response status. cons.gridx++; cons.anchor = GridBagConstraints.CENTER; cons.weightx = 1; cons.fill = GridBagConstraints.NONE; Component statusLabel = ch.statusLabel_; layer.setConstraints( statusLabel, cons ); resultBox.add( statusLabel ); // Detail button. cons.gridx++; cons.anchor = GridBagConstraints.WEST; cons.weightx = 0; cons.fill = GridBagConstraints.NONE; Component detailButton = new JButton( ch.responseDetailAction_ ); layer.setConstraints( detailButton, cons ); resultBox.add( detailButton ); // Prepare for next row. cons.gridy++; cons.gridx = 0; } main.add( resultBox, BorderLayout.CENTER ); // Display popup window. pack(); if ( parent != null ) { setLocationRelativeTo( parent ); } setVisible( true ); } public void result( Client client, Response response ) { ClientHandler ch = (ClientHandler) clientMap_.get( client ); if ( ch == null ) { throw new IllegalArgumentException( "Shouldn't happen" ); } else { ch.result( response ); } } public void done() { // Any clients which have not received responses yet, will not do so. // Make this clear in the status labels. for ( Iterator it = clientMap_.entrySet().iterator(); it.hasNext(); ) { ClientHandler ch = (ClientHandler) ((Map.Entry) it.next()).getValue(); if ( ch.response_ == null ) { ch.result( null ); } } // Arrange for auto close of window if required. if ( isVisible() && closeDelayMillis_ >= 0 && autoCloser_.isSelected() ) { if ( closeDelayMillis_ == 0 ) { dispose(); } else { new Timer( closeDelayMillis_, new ActionListener() { public void actionPerformed( ActionEvent evt ) { if ( autoCloser_.isSelected() ) { dispose(); } } } ).start(); } } } /** * Handles per-client state for message wait/response information. */ private class ClientHandler { final JLabel nameLabel_; final JLabel statusLabel_; final Action responseDetailAction_; Response response_; /** * Constructor. * * @param client client */ ClientHandler( Client client ) { final String cName = client.toString(); nameLabel_ = new JLabel( cName ); statusLabel_ = new JLabel( "... Waiting ..." ); statusLabel_.setForeground( new Color( 0x40, 0x40, 0x40 ) ); responseDetailAction_ = new DetailAction( "Detail", "Show details of response", cName + " Response", "SAMP Response from client " + cName + ":" ) { protected Map getSampMap() { return response_; } }; responseDetailAction_.setEnabled( response_ != null ); } /** * Updates status according to a received response. * If the response is null, it indicates that no response is expected * in the future. * * @param response received response, or null */ public void result( Response response ) { response_ = response; String status; String tip; boolean success; if ( response_ == null ) { success = false; status = "Aborted"; tip = "Interruption in messaging system prevented " + "receipt of response?"; } else if ( response.isOK() ) { success = true; status = "OK"; tip = "Message processed successfully"; } else { success = false; status = "Fail"; ErrInfo errInfo = response.getErrInfo(); String errtxt = errInfo == null ? null : errInfo.getErrortxt(); tip = errtxt; if ( errtxt != null ) { status += " (" + errtxt + ")"; } } statusLabel_.setText( status ); statusLabel_.setForeground( success ? new Color( 0, 0x80, 0 ) : new Color( 0x80, 0, 0 ) ); statusLabel_.setToolTipText( tip ); responseDetailAction_.setEnabled( response_ != null ); } } /** * Action which will display a SampMap in a popup window. */ private abstract class DetailAction extends AbstractAction { private final String popupTitle_; private final String heading_; /** * Constructor. * * @param name action name * @param shortDesc action short description (tool tip) * @param popupTitle title of popup window */ DetailAction( String name, String shortDesc, String popupTitle, String heading ) { super( name ); putValue( SHORT_DESCRIPTION, shortDesc ); popupTitle_ = popupTitle; heading_ = heading; } /** * Returns the map object which is to be displayed. * Invoked by {@link #actionPerformed}. * * @return object to display */ protected abstract Map getSampMap(); public void actionPerformed( ActionEvent evt ) { // Make sure that autoclose is off, since otherwise these dialogues // will get disappeared at the same time as the owner window, // which is probably not what the user would expect. autoCloser_.setSelected( false ); // Set up a text component containing the full map serialization. JTextArea ta = new JTextArea(); ta.setLineWrap( false ); ta.setEditable( false ); ta.append( SampUtils.formatObject( getSampMap(), 3 ) ); ta.setCaretPosition( 0 ); // Wrap it in a scroll pane and size appropriately. Dimension size = ta.getPreferredSize(); size.height = Math.min( size.height + 20, 200 ); size.width = Math.min( size.width + 20, 360 ); JScrollPane scroller = new JScrollPane( ta ); scroller.setPreferredSize( size ); // Put into a non-modal dialogue with decorations. final JDialog dialog = new JDialog( PopupResultHandler.this, popupTitle_, false ); dialog.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE ); JComponent main = new JPanel( new BorderLayout() ); main.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); dialog.getContentPane().add( main ); Action closeAction = new AbstractAction( "Close" ) { public void actionPerformed( ActionEvent evt ) { dialog.dispose(); } }; Box buttonBox = Box.createHorizontalBox(); buttonBox.add( Box.createHorizontalGlue() ); buttonBox.add( new JButton( closeAction ) ); buttonBox.add( Box.createHorizontalGlue() ); Box headBox = Box.createHorizontalBox(); headBox.add( new JLabel( heading_ ) ); headBox.add( Box.createHorizontalGlue() ); headBox.setBorder( BorderFactory.createEmptyBorder( 0, 0, 5, 0 ) ); buttonBox.setBorder( BorderFactory .createEmptyBorder( 5, 0, 0, 0 ) ); main.add( headBox, BorderLayout.NORTH ); main.add( buttonBox, BorderLayout.SOUTH ); main.add( scroller, BorderLayout.CENTER ); // Post the dialogue. dialog.setLocationRelativeTo( PopupResultHandler.this ); dialog.pack(); dialog.setVisible( true ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/SampThread.java000066400000000000000000000041651356450004300246660ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.util.EventObject; import javax.swing.SwingUtilities; import org.astrogrid.samp.client.SampException; /** * Helper class for performing a SAMP operation from within the * Event Dispatch Thread. * You must implement the {@link #runSamp} method to do the work; * any resulting SampException will be suitably displayed to the user. * * @author Mark Taylor * @since 27 Jul 2011 */ abstract class SampThread extends Thread { private final Component parent_; private final String errTitle_; private final String errText_; /** * Constructs a SampThread given a parent component. * Arguments are required for posting an error if one occurs. * * @param parent parent component * @param errTitle title of error window if one is needed * @param errText text of error messsage if one is needed */ public SampThread( Component parent, String errTitle, String errText ) { super( "SAMP call" ); parent_ = parent; errTitle_ = errTitle; errText_ = errText; } /** * Constructs a SampThread given an event object with a source which * presumably corresponds to a parent component. * * @param evt triggering event * @param errTitle title of error window if one is needed * @param errText text of error messsage if one is needed */ public SampThread( EventObject evt, String errTitle, String errText ) { this( evt.getSource() instanceof Component ? (Component) evt.getSource() : null, errTitle, errText ); } /** * Called from the {@link #run} method. */ protected abstract void sampRun() throws SampException; public void run() { try { sampRun(); } catch ( final SampException e ) { SwingUtilities.invokeLater( new Runnable() { public void run() { ErrorDialog.showError( parent_, errTitle_, errText_, e ); } } ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/SelectiveClientListModel.java000066400000000000000000000125651356450004300275400ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.util.Arrays; import javax.swing.AbstractListModel; import javax.swing.ListModel; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import org.astrogrid.samp.Client; /** * ListModel implementation which sits on top of an existing ListModel * containing {@link org.astrogrid.samp.Client}s, but only includes a * subset of its elements. * *

Concrete subclasses must *

    *
  1. implement the {@link #isIncluded} method to determine which clients * from the base list appear in this one
  2. *
  3. call {@link #init} before the class is used * (for instance in their constructor)
  4. *
* * @author Mark Taylor * @since 1 Sep 2008 */ public abstract class SelectiveClientListModel extends AbstractListModel { private final ListModel baseModel_; private final ListDataListener listDataListener_; private int[] map_; /** * Constructor. * * @param clientListModel base ListModel containing * {@link org.astrogrid.samp.Client} objects */ public SelectiveClientListModel( ListModel clientListModel ) { baseModel_ = clientListModel; // Somewhat haphazard implementation. The ListDataListener interface // is not constructed (or documented) so as to make it easy to // fire the right events. Some efficiency measures are taken here, // but it would be possible to do more. listDataListener_ = new ListDataListener() { public void contentsChanged( ListDataEvent evt ) { int[] oldMap = map_; map_ = calculateMap(); if ( Arrays.equals( oldMap, map_ ) && evt.getIndex0() == evt.getIndex1() && evt.getIndex0() >= 0 ) { int index = evt.getIndex0(); for ( int i = 0; i < map_.length; i++ ) { if ( map_[ i ] == index ) { fireContentsChanged( this, index, index ); } } } else { fireContentsChanged( this, -1, -1 ); } } public void intervalAdded( ListDataEvent evt ) { int[] oldMap = map_; map_ = calculateMap(); if ( ! Arrays.equals( oldMap, map_ ) ) { int leng = Math.min( map_.length, oldMap.length ); int index0 = -1; for ( int i = 0; i < leng; i++ ) { if ( oldMap[ i ] != map_[ i ] ) { index0 = i; break; } } int index1 = -1; for ( int i = 0; i < leng; i++ ) { if ( oldMap[ oldMap.length - 1 - i ] != map_[ map_.length - 1 - i ] ) { index1 = map_.length - 1 - i; break; } } if ( index0 >= 0 && index1 >= 0 ) { fireIntervalAdded( this, index0, index1 ); } else { fireContentsChanged( this, -1, -1 ); } } } public void intervalRemoved( ListDataEvent evt ) { int[] oldMap = map_; map_ = calculateMap(); if ( ! Arrays.equals( oldMap, map_ ) ) { fireContentsChanged( this, -1, -1 ); } } }; } /** * Implement this method to determine which clients are included in * this list. * * @param client client for consideration * @return true iff client is to be included in this list */ protected abstract boolean isIncluded( Client client ); /** * Must be called by subclass prior to use. */ protected void init() { refresh(); baseModel_.addListDataListener( listDataListener_ ); } /** * Recalculates the inclusions. This should be called if the return * value from {@link #isIncluded} might have changed for some of the * elements. */ protected void refresh() { map_ = calculateMap(); } public int getSize() { return map_.length; } public Object getElementAt( int index ) { return baseModel_.getElementAt( map_[ index ] ); } /** * Releases any resources associated with this transmitter. */ public void dispose() { baseModel_.removeListDataListener( listDataListener_ ); } /** * Recalculates the this list -> base list lookup table. * * @return array whose indices represent elements of this list, and * values represent elements of the base list */ private int[] calculateMap() { int nc = baseModel_.getSize(); int[] map = new int[ nc ]; int ij = 0; for ( int ic = 0; ic < nc; ic++ ) { Client client = (Client) baseModel_.getElementAt( ic ); if ( isIncluded( client ) ) { map[ ij++ ] = ic; } } int[] map1 = new int[ ij ]; System.arraycopy( map, 0, map1, 0, ij ); return map1; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/SendActionManager.java000066400000000000000000000322421356450004300261550ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.AbstractListModel; import javax.swing.Action; import javax.swing.ComboBoxModel; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JMenu; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.ListModel; import javax.swing.SwingUtilities; import org.astrogrid.samp.Client; /** * Manages actions to send SAMP messages to one or all recipients. * The main useful trick that this class can do is to maintain one or * more menus for sending messages to suitable recipients. * The contents of these menus are updated automatically depending on * the subscriptions of all the currently registered SAMP clients. * *

Note: concrete subclasses must call {@link #updateState} before use * (in the constructor). * * @author Mark Taylor * @since 2 Sep 2008 */ public abstract class SendActionManager { private final GuiHubConnector connector_; final ListModel subscribedClientModel_; private final List menuList_; private final ListDataListener subscriptionListener_; private final ChangeListener connectionListener_; private boolean enabled_; private Action broadcastAct_; private boolean broadcastActCreated_; private Action[] sendActs_; private static Icon SEND_ICON; private static Icon BROADCAST_ICON; private static final Logger logger_ = Logger.getLogger( SendActionManager.class.getName() ); /** ComboBox element indicating broadcast to all clients. */ public static final String BROADCAST_TARGET = "All Clients"; /** * Constructor. * * @param connector hub connector * @param clientListModel list model containing only those * clients which are suitable recipients; * all elements must be {@link org.astrogrid.samp.Client}s */ protected SendActionManager( GuiHubConnector connector, ListModel clientListModel ) { connector_ = connector; subscribedClientModel_ = clientListModel; subscriptionListener_ = new ListDataListener() { public void intervalAdded( ListDataEvent evt ) { updateState(); } public void intervalRemoved( ListDataEvent evt ) { updateState(); } public void contentsChanged( ListDataEvent evt ) { updateState(); } }; subscribedClientModel_.addListDataListener( subscriptionListener_ ); // Ensure that changes to the connection status are reflected. connectionListener_ = new ChangeListener() { public void stateChanged( ChangeEvent evt ) { updateEnabledness(); } }; connector.addConnectionListener( connectionListener_ ); // Initialise other state. enabled_ = true; menuList_ = new ArrayList(); } /** * Returns a new action for broadcast associated with this object. * The enabled status of the action will be managed by this object. * * @return broadcast action; may be null if broadcast is not required */ protected abstract Action createBroadcastAction(); /** * Returns an action which can perform a single-client send associated * with this object. If it implements equals * (and hashCode) intelligently there will be efficiency * advantages. * The enabled status of such actions will be managed by this object. * * @param client recipient client * @return action which sends to the given client */ protected abstract Action getSendAction( Client client ); /** * Sets the enabled status of this object. This acts as a restriction * (AND) on the enabled status of the menus and actions controlled by * this object. If there are no suitable recipient applications * registered they will be disabled anyway. * * @param enabled false to ensure that the actions are disabled, * true means they may be enabled */ public void setEnabled( boolean enabled ) { enabled_ = enabled; updateEnabledness(); } /** * Returns an action which will broadcast a message * to all suitable registered applications. * *

This action is currently not disabled when there are no suitable * listeners, mainly for debugging purposes (so you can see if a * message is getting sent and what it looks like even in absence of * suitable listeners). * * @return broadcast action */ public Action getBroadcastAction() { if ( ! broadcastActCreated_ ) { broadcastAct_ = createBroadcastAction(); broadcastActCreated_ = true; updateEnabledness(); } return broadcastAct_; } /** * Returns a new menu which provides options to send a message to * one of the registered listeners at a time. This menu will be * disabled when no suitable listeners are registered. * * @param name menu title * @return new message send menu */ public JMenu createSendMenu( String name ) { JMenu menu = new JMenu( name ); for ( int is = 0; is < sendActs_.length; is++ ) { menu.add( sendActs_[ is ] ); } menuList_.add( menu ); updateEnabledness(); return menu; } /** * Releases any resources associated with a menu previously created * using {@link #createSendMenu}. Don't use the menu again. * * @param menu previously created send menu */ public void disposeSendMenu( JMenu menu ) { menuList_.remove( menu ); } /** * Releases any resources associated with this object. */ public void dispose() { subscribedClientModel_.removeListDataListener( subscriptionListener_ ); if ( subscribedClientModel_ instanceof SubscribedClientListModel ) { ((SubscribedClientListModel) subscribedClientModel_).dispose(); } connector_.removeConnectionListener( connectionListener_ ); } /** * Updates the state of actions managed by this object when the * list of registered listeners has changed. */ public void updateState() { // Get a list of actions for the currently subscribed clients. int nsub = subscribedClientModel_.getSize(); Action[] sendActs = new Action[ nsub ]; for ( int ia = 0; ia < nsub; ia++ ) { sendActs[ ia ] = getSendAction( (Client) subscribedClientModel_.getElementAt( ia ) ); } // Update menus if required. if ( ! Arrays.equals( sendActs, sendActs_ ) ) { sendActs_ = sendActs; for ( Iterator menuIt = menuList_.iterator(); menuIt.hasNext(); ) { JMenu menu = (JMenu) menuIt.next(); menu.removeAll(); for ( int is = 0; is < sendActs.length; is++ ) { menu.add( sendActs[ is ] ); } } updateEnabledness(); } } /** * Returns the client list to which this manager will offer sends. * * @return listmodel whose elements are suitably subscribed {@link Client}s */ public ListModel getClientListModel() { return subscribedClientModel_; } /** * Returns a new ComboBoxModel containing selections for each suitable * client and an additional selection for broadcast to all clients. * Elements are {@link org.astrogrid.samp.Client} objects, or * {@link #BROADCAST_TARGET} to indicate broadcast. * The result of this is suitable for use with {@link #createTargetAction}. * * @return new client combo box model */ public ComboBoxModel createTargetSelector() { return new TargetComboBoxModel( subscribedClientModel_ ); } /** * Returns an action suitable for sending the message represented by * this manager to a target selected by a supplied ComboBoxModel. * This model is typically the result of calling * {@link #createTargetSelector}. * * @param targetSelector combo box model in which the elements are * {@link org.astrogrid.samp.Client} objects, * or {@link #BROADCAST_TARGET} null to indicate broadcast */ public Action createTargetAction( final ComboBoxModel targetSelector ) { return new AbstractAction( "Send to selected target" ) { public void actionPerformed( ActionEvent evt ) { Object target = targetSelector.getSelectedItem(); if ( target instanceof Client ) { getSendAction( (Client) target ).actionPerformed( evt ); } else if ( BROADCAST_TARGET.equals( target ) ) { getBroadcastAction().actionPerformed( evt ); } else { Toolkit.getDefaultToolkit().beep(); logger_.warning( "Unknown send target: " + target + " - no action" ); } } }; } /** * Returns this manager's hub connector. * * @return connector */ public GuiHubConnector getConnector() { return connector_; } /** * Updates the enabled status of controlled actions in accordance with * this object's current state. */ private void updateEnabledness() { boolean active = enabled_ && connector_.isConnected() && sendActs_.length > 0; if ( broadcastAct_ != null ) { broadcastAct_.setEnabled( active ); } for ( Iterator it = menuList_.iterator(); it.hasNext(); ) { ((JMenu) it.next()).setEnabled( active ); } } /** * Returns an icon suitable for depicting a general targetted send. * * @return send icon */ public static Icon getSendIcon() { if ( SEND_ICON == null ) { SEND_ICON = IconStore.createResourceIcon( "phone2.gif" ); } return SEND_ICON; } /** * Returns an icon suitable for depicting a general broadcast send. * * @return broadcast icon */ public static Icon getBroadcastIcon() { if ( BROADCAST_ICON == null ) { BROADCAST_ICON = IconStore.createResourceIcon( "tx3.gif" ); } return BROADCAST_ICON; } /** * ComboBoxModel implementation used for selecting a target client. * It essentiall mirrors an existing client model but prepends a * broadcast option. */ private static class TargetComboBoxModel extends AbstractListModel implements ComboBoxModel { private final ListModel clientListModel_; private Object selectedItem_ = BROADCAST_TARGET; /** * Constructor. * * @param clientListModel list model containing suitable * {@link org.astrogrid.samp.Client}s */ TargetComboBoxModel( ListModel clientListModel ) { clientListModel_ = clientListModel; /* Watch the underlying client model for changes and * update this one accordingly. */ clientListModel_.addListDataListener( new ListDataListener() { public void contentsChanged( ListDataEvent evt ) { fireContentsChanged( evt.getSource(), adjustIndex( evt.getIndex0() ), adjustIndex( evt.getIndex1() ) ); } public void intervalAdded( ListDataEvent evt ) { fireIntervalAdded( evt.getSource(), adjustIndex( evt.getIndex0() ), adjustIndex( evt.getIndex1() ) ); } public void intervalRemoved( ListDataEvent evt ) { fireIntervalRemoved( evt.getSource(), adjustIndex( evt.getIndex0() ), adjustIndex( evt.getIndex1() ) ); } private int adjustIndex( int index ) { return index >= 0 ? index + 1 : index; } } ); } public int getSize() { return clientListModel_.getSize() + 1; } public Object getElementAt( int index ) { return index == 0 ? BROADCAST_TARGET : clientListModel_.getElementAt( index - 1 ); } public Object getSelectedItem() { return selectedItem_; } public void setSelectedItem( Object item ) { selectedItem_ = item; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/SubscribedClientListModel.java000066400000000000000000000054071356450004300276770ustar00rootroot00000000000000package org.astrogrid.samp.gui; import org.astrogrid.samp.Client; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * Selective client list model which contains only those non-self clients * which are subscribed to one or more of a given list of MTypes. * * @author Mark Taylor * @since 1 Sep 2008 */ public class SubscribedClientListModel extends SelectiveClientListModel { private final GuiHubConnector connector_; private String[] mtypes_; /** * Constructor for multiple MTypes. * * @param connector hub connector * @param mtypes mtypes of interest (may have wildcards) */ public SubscribedClientListModel( GuiHubConnector connector, String[] mtypes ) { super( connector.getClientListModel() ); connector_ = connector; mtypes_ = (String[]) mtypes.clone(); init(); } /** * Constructor for single MType. * * @param connector hub connector * @param mtype mtype of interest (may have wildcards) */ public SubscribedClientListModel( GuiHubConnector connector, String mtype ) { this( connector, new String[] { mtype } ); } /** * Sets the list of MTypes which defines the elements of this list. * Any client subscribed to one or more of these MTypes is included. * * @param mtypes new MType list */ public void setMTypes( String[] mtypes ) { mtypes_ = (String[]) mtypes.clone(); refresh(); fireContentsChanged( this, -1, -1 ); } /** * Returns the list of MTypes which defines the elements of this list. * * @return MType list */ public String[] getMTypes() { return mtypes_; } /** * Returns true if client is subscribed to one of this * model's MTypes. */ protected boolean isIncluded( Client client ) { String selfId; try { HubConnection connection = connector_.getConnection(); if ( connection == null ) { return false; } else { selfId = connection.getRegInfo().getSelfId(); } } catch ( SampException e ) { return false; } if ( client.getId().equals( selfId ) ) { return false; } Subscriptions subs = client.getSubscriptions(); if ( subs != null ) { for ( int im = 0; im < mtypes_.length; im++ ) { if ( subs.isSubscribed( mtypes_[ im ] ) ) { return true; } } } return false; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/SysTray.java000066400000000000000000000176001356450004300242520ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.AWTException; import java.awt.Image; import java.awt.PopupMenu; import java.awt.event.ActionListener; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.logging.Logger; /** * Provides basic access to the windowing system's System Tray. * This is a facade for a subset of the Java 1.6 java.awt.SystemTray * functionality. When running in a J2SE1.6 JRE it will use reflection * to access the underlying classes. In an earlier JRE, it will report * lack of support. * * @author Mark Taylor * @since 20 Jul 2010 */ public abstract class SysTray { /** * Name of system property ({@value}) to inhibit use of system tray; * if set "true" the system tray will not be used even if it is * apparently supported. */ public static final String NOTRAY_PROP = "jsamp.nosystray"; private static SysTray instance_; private static final Logger logger_ = Logger.getLogger( SysTray.class.getName() ); /** * Indicates whether system tray functionality is available. * * @return true iff the addIcon/removeIcon methods are expected to work */ public abstract boolean isSupported(); /** * Adds an icon to the system tray. * * @param im image for display * @param tooltip tooltip text, or null * @param popup popup menu, or null * @param iconListener listener triggered when icon is activated, or null * @return tray icon object, may be used for later removal */ public abstract Object addIcon( Image im, String tooltip, PopupMenu popup, ActionListener iconListener ) throws AWTException; /** * Removes a previously-added icon from the tray. * * @param trayIcon object obtained from a previous invocation of * addIcon */ public abstract void removeIcon( Object trayIcon ) throws AWTException; /** * Returns an instance of this class. * * @return instance */ public static SysTray getInstance() { if ( instance_ == null ) { String jvers = System.getProperty( "java.specification.version" ); boolean isJava6 = jvers != null && jvers.matches( "^[0-9]+\\.[0-9]+$" ) && Double.parseDouble( jvers ) > 1.5999; if ( ! isJava6 ) { logger_.info( "Not expecting system tray support" + " (java version < 1.6)" ); } SysTray instance; String notrayProp = System.getProperty( NOTRAY_PROP ); if ( "true".equalsIgnoreCase( notrayProp ) ) { logger_.info( "No system tray support by explicit request" + " (" + NOTRAY_PROP + ")" ); instance = new NoSysTray(); } else { try { instance = new Java6SysTray(); } catch ( Throwable e ) { if ( isJava6 ) { logger_.info( "No system tray support: " + e ); } instance = new NoSysTray(); } } instance_ = instance; } return instance_; } /** * Implementation which provides no system tray access. */ private static class NoSysTray extends SysTray { public boolean isSupported() { return false; } public Object addIcon( Image im, String tooltip, PopupMenu popup, ActionListener iconListener ) { throw new UnsupportedOperationException(); } public void removeIcon( Object trayIcon ) { throw new UnsupportedOperationException(); } } /** * Implementation which provides system tray access using J2SE 1.6 classes * by reflection. */ private static class Java6SysTray extends SysTray { private final Class systemTrayClass_; private final Method addMethod_; private final Method removeMethod_; private final Class trayIconClass_; private final Constructor trayIconConstructor_; private final Method setImageAutoSizeMethod_; private final Method addActionListenerMethod_; private final Object systemTrayInstance_; /** * Constructor. */ Java6SysTray() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { systemTrayClass_ = Class.forName( "java.awt.SystemTray" ); trayIconClass_ = Class.forName( "java.awt.TrayIcon" ); addMethod_ = systemTrayClass_ .getMethod( "add", new Class[] { trayIconClass_ } ); removeMethod_ = systemTrayClass_ .getMethod( "remove", new Class[] { trayIconClass_ } ); trayIconConstructor_ = trayIconClass_ .getConstructor( new Class[] { Image.class, String.class, PopupMenu.class } ); setImageAutoSizeMethod_ = trayIconClass_ .getMethod( "setImageAutoSize", new Class[] { boolean.class } ); addActionListenerMethod_ = trayIconClass_ .getMethod( "addActionListener", new Class[] { ActionListener.class } ); boolean isSupported = Boolean.TRUE .equals( systemTrayClass_ .getMethod( "isSupported", new Class[ 0 ] ) .invoke( null, new Object[ 0 ] ) ); systemTrayInstance_ = isSupported ? systemTrayClass_ .getMethod( "getSystemTray", new Class[ 0 ] ) .invoke( null, new Object[ 0 ] ) : null; } public boolean isSupported() { return systemTrayInstance_ != null; } public Object addIcon( Image im, String tooltip, PopupMenu popup, ActionListener iconListener ) throws AWTException { try { Object trayIcon = trayIconConstructor_ .newInstance( new Object[] { im, tooltip, popup } ); setImageAutoSizeMethod_ .invoke( trayIcon, new Object[] { Boolean.TRUE } ); if ( iconListener != null ) { addActionListenerMethod_ .invoke( trayIcon, new Object[] { iconListener } ); } addMethod_.invoke( systemTrayInstance_, new Object[] { trayIcon } ); return trayIcon; } catch ( InvocationTargetException e ) { String msg = e.getCause() instanceof AWTException ? e.getCause().getMessage() : "Add tray icon invocation failed"; throw (AWTException) new AWTException( msg ).initCause( e ); } catch ( Exception e ) { throw (AWTException) new AWTException( "Add tray icon invocation failed" ) .initCause( e ); } } public void removeIcon( Object trayIcon ) throws AWTException { try { removeMethod_.invoke( systemTrayInstance_, new Object[] { trayIcon } ); } catch ( Exception e ) { throw (AWTException) new AWTException( "Remove tray icon invocation failed" ) .initCause( e ); } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/Transmission.java000066400000000000000000000252531356450004300253300ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Icon; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; /** * Describes the properties of a message which has been sent from one * client to another. Methods which might change the state of instances * of this class should be invoked only from the AWT event dispatch thread. * * @author Mark Taylor * @since 20 Nov 2008 */ public class Transmission { private final Client sender_; private final Client receiver_; private final Message msg_; private final String msgId_; private final String msgTag_; private final List listenerList_; private final ChangeEvent evt_; private Response response_; private Throwable error_; private boolean senderUnreg_; private boolean receiverUnreg_; private long doneTime_; private static final Logger logger_ = Logger.getLogger( Transmission.class.getName() ); /** * Constructor. * * @param sender sender * @param receiver receiver * @param msg message * @param msgTag message tag * @param msgId message ID */ public Transmission( Client sender, Client receiver, Message msg, String msgTag, String msgId ) { sender_ = sender; receiver_ = receiver; msg_ = msg; msgTag_ = msgTag; msgId_ = msgId; listenerList_ = new ArrayList(); evt_ = new ChangeEvent( this ); doneTime_ = Long.MAX_VALUE; } /** * Returns the client which sent this transmission. * * @return sender */ public Client getSender() { return sender_; } /** * Returns the client to which this transmission was sent. * * @return receiver */ public Client getReceiver() { return receiver_; } /** * Returns the message which was sent. * * @return message */ public Message getMessage() { return msg_; } /** * Returns the message tag corresponding to this transmission. * Will be null for notify-type sends. * * @return msg tag */ public String getMessageTag() { return msgTag_; } /** * Returns the message ID associated with this message. * This is the identifier passed to the receiver which it uses to * match messages with responses; it will be null iff the transmission * used the notify delivery pattern (no response expected). * * @return msgId; possibly null */ public String getMessageId() { return msgId_; } /** * Sets the response for this transmission. * * @param response response */ public void setResponse( Response response ) { response_ = response; fireChange(); } /** * Returns the response for this transmission. * Will be null if no response has (yet) arrived. * * @return response */ public Response getResponse() { return response_; } /** * Associates an error with this transmission. * This is probably an indication that the send failed or some other * non-SAMP event intervened to prevent normal resolution. * * @param error throwable causing the failure */ public void setError( Throwable error ) { error_ = error; logger_.log( Level.WARNING, "Error in hub operation: " + error.getMessage(), error ); fireChange(); } /** * Returns a Throwable which prevented normal resolution of this * transmission. * * @return error */ public Throwable getError() { return error_; } /** * Indicates that the sender of this transmission has unregistered. */ public void setSenderUnregistered() { senderUnreg_ = true; fireChange(); } /** * Indicates that the receiver of this transmission has unregistered. */ public void setReceiverUnregistered() { receiverUnreg_ = true; fireChange(); } /** * Returns the epoch at which this transmission was completed. * If it is still pending ({@link #isDone()}==false), * the returned value will be (way) in the future. * * @return value of System.currentTimeMillis() at which * {@link #isDone} first returned true */ public long getDoneTime() { return doneTime_; } /** * Indicates whether further changes to the state of this object * are expected, that is if a response/failure is yet to be received. * * @return true iff no further changes are expected */ public boolean isDone() { return error_ != null || response_ != null || ( msgId_ == null && msgTag_ == null ) || receiverUnreg_; } /** * Returns an object which describes the current status of this * transmission in terms which can be presented to the GUI. */ public Status getStatus() { if ( error_ != null ) { return Status.EXCEPTION; } else if ( response_ != null ) { String status = response_.getStatus(); if ( Response.OK_STATUS.equals( status ) ) { return Status.OK; } else if ( Response.WARNING_STATUS.equals( status ) ) { return Status.WARNING; } else if ( Response.ERROR_STATUS.equals( status ) ) { return Status.ERROR; } else if ( status == null ) { return Status.NONE; } else { return new Status( "Completed (" + status + ")", Status.WARNING_COLOR, true ); } } else if ( msgId_ == null && msgTag_ == null ) { return Status.NOTIFIED; } else if ( receiverUnreg_ ) { return Status.ORPHANED; } else { assert ! isDone(); return Status.PENDING; } } /** * Adds a listener which will be notified if the state of this transmission * changes (if a response or failure is signalled). * The {@link javax.swing.event.ChangeEvent}s sent to these listeners * will have a source which is this Transmission. * * @param listener listener to add */ public void addChangeListener( ChangeListener listener ) { listenerList_.add( listener ); } /** * Removes a listener previously added by {@link #addChangeListener}. * * @param listener listener to remove */ public void removeChangeListener( ChangeListener listener ) { listenerList_.remove( listener ); } /** * Notifies listeners of a state change. */ private void fireChange() { if ( doneTime_ == Long.MAX_VALUE && isDone() ) { doneTime_ = System.currentTimeMillis(); } for ( Iterator it = listenerList_.iterator(); it.hasNext(); ) { ChangeListener listener = (ChangeListener) it.next(); listener.stateChanged( evt_ ); } } /** * Describes the status of a transmission in terms that can be * presented in the GUI. */ public static class Status { private final String text_; private final Color iconColor_; private final boolean isDone_; private final static Color PENDING_COLOR = Color.BLACK; private final static Color OK_COLOR = new Color( 0x00c000 ); private final static Color ERROR_COLOR = new Color( 0xc00000 ); private final static Color WARNING_COLOR = new Color( 0x806030 ); private final static Color NOTIFY_COLOR = new Color( 0x808080 ); private final static Status OK = new Status( "Success", OK_COLOR, true ); private final static Status WARNING = new Status( "Warning", WARNING_COLOR, true ); private final static Status ERROR = new Status( "Error", ERROR_COLOR, true ); private final static Status NONE = new Status( "Completed (??)", WARNING_COLOR, true ); private final static Status NOTIFIED = new Status( "Notified", NOTIFY_COLOR, true ); private final static Status EXCEPTION = new Status( "Exception", ERROR_COLOR, true ); private final static Status ORPHANED = new Status( "Orphaned", WARNING_COLOR, true ); private final static Status PENDING = new Status( "...pending...", PENDING_COLOR, false ); /** * Constructor. * * @param text short status summary * @param iconColor colour to plot icon * @param isDone whether status represents completed processing */ Status( String text, Color iconColor, boolean isDone ) { text_ = text; iconColor_ = iconColor; isDone_ = isDone; } /** * Returns the text for this status. * * @return short summmary */ public String getText() { return text_; } /** * Returns a little icon representing status. * * @param height required height of icon * @return icon */ public Icon getIcon( final int height ) { final int width = (int) Math.floor( 0.866 * height ); return new Icon() { public void paintIcon( Component c, Graphics g, int x, int y ) { int[] xs = new int[] { x, x + width, x, }; int[] ys = new int[] { y, y + height / 2, y + height, }; Color gcolor = g.getColor(); g.setColor( iconColor_ ); if ( isDone_ ) { g.drawPolygon( xs, ys, 3 ); } else { g.fillPolygon( xs, ys, 3 ); } g.setColor( gcolor ); } public int getIconWidth() { return width; } public int getIconHeight() { return height; } }; } public String toString() { return text_; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionCellRenderer.java000066400000000000000000000023661356450004300276170ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.awt.Graphics; import javax.swing.Icon; /** * CellRenderer for transmission objects. * * @author Mark Taylor * @since 27 Nov 2008 */ class TransmissionCellRenderer implements IconBox.CellRenderer { public Icon getIcon( IconBox iconBox, Object value, int index ) { final int size = iconBox.getTransverseSize(); if ( value instanceof Transmission ) { return ((Transmission) value).getStatus().getIcon( size ); } else { return new Icon() { public void paintIcon( Component c, Graphics g, int x, int y ) { int s = size - 3 + ( size % 2 ); g.drawOval( x + 1, y + 1, s, s ); } public int getIconWidth() { return size; } public int getIconHeight() { return size; } }; } } public String getToolTipText( IconBox iconBox, Object value, int index ) { if ( value instanceof Transmission ) { return ((Transmission) value).getMessage().getMType(); } else { return null; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionListIcon.java000066400000000000000000000213651356450004300267750ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseEvent; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.ListModel; import javax.swing.ToolTipManager; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; /** * Icon which paints a graphical representation of a list of Transmissions. * * @author Mark Taylor * @since 21 Nov 2008 */ public class TransmissionListIcon implements Icon { private final ListModel rxModel_; private final ListModel txModel_; private final int size_; private final int transIconWidth_; private final Icon targetIcon_; /** * Constructor. * * @param rxModel list of messages received; * all elements must be {@link Transmission} objects * @param txModel list of messages sent; * all elements must be {@link Transmission} objects * @param size height of icon in pixels; this also scales the width */ public TransmissionListIcon( ListModel rxModel, ListModel txModel, int size ) { rxModel_ = rxModel; txModel_ = txModel; size_ = size; transIconWidth_ = (int) Math.floor( size_ * 0.866 ); // equilateral final boolean hasTx = txModel_ != null; final boolean hasRx = rxModel_ != null; targetIcon_ = new Icon() { public int getIconWidth() { return size_; } public int getIconHeight() { return size_; } public void paintIcon( Component c, Graphics g, int x, int y ) { g.drawOval( x + 1, y + 1, size_ - 2, size_ - 2 ); } }; } /** * Returns the transmission (if any) which is painted at a given point. * * @param point screen point relative to the origin of this icon * @return transmission painted at point or null if there * isn't one */ public Transmission getTransmissionAt( Point point ) { int x = point.x; int y = point.y; if ( x < 0 || x > getIconWidth() || y < 0 || y > getIconHeight() ) { return null; } int x0 = 0; int rxWidth = rxModel_.getSize() * transIconWidth_; if ( x - x0 < rxWidth ) { int ir = ( x - x0 ) / transIconWidth_; return (Transmission) rxModel_.getElementAt( ir ); } x0 += rxWidth; int targetWidth = targetIcon_.getIconWidth(); if ( x - x0 < targetWidth ) { return null; } x0 += targetWidth; int txWidth = txModel_.getSize() * transIconWidth_; if ( x - x0 < txWidth ) { int it = ( x - x0 ) / transIconWidth_; return (Transmission) txModel_.getElementAt( it ); } x0 += txWidth; assert x > x0; return null; } public int getIconWidth() { return ( rxModel_ != null ? rxModel_.getSize() * transIconWidth_ : 0 ) + targetIcon_.getIconWidth() + ( txModel_ != null ? txModel_.getSize() * transIconWidth_ : 0 ); } public int getIconHeight() { return size_; } public void paintIcon( Component c, Graphics g, int x, int y ) { if ( rxModel_ != null ) { for ( int i = 0; i < rxModel_.getSize(); i++ ) { Transmission trans = (Transmission) rxModel_.getElementAt( i ); Icon transIcon = getTransIcon( trans, false ); transIcon.paintIcon( c, g, x, y ); x += transIcon.getIconWidth(); } } targetIcon_.paintIcon( c, g, x, y ); x += targetIcon_.getIconWidth(); if ( txModel_ != null ) { for ( int i = 0; i < txModel_.getSize(); i++ ) { Transmission trans = (Transmission) txModel_.getElementAt( i ); Icon transIcon = getTransIcon( trans, true ); transIcon.paintIcon( c, g, x, y ); x += transIcon.getIconWidth(); } } } /** * Returns an icon which can paint a particular transmission. * * @param trans transmission * @param isTx true if trans represents a send, * false if it represents a receive */ private Icon getTransIcon( Transmission trans, final boolean isTx ) { return new Icon() { public int getIconHeight() { return size_; } public int getIconWidth() { return transIconWidth_; } public void paintIcon( Component c, Graphics g, int x, int y ) { int xlo = x + 1; int xhi = x + transIconWidth_ - 1; int[] xs = isTx ? new int[] { xhi, xlo, xhi, } : new int[] { xlo, xhi, xlo, }; int[] ys = new int[] { y, y + size_ / 2, y + size_ - 1 }; g.fillPolygon( xs, ys, 3 ); } }; } public JComponent createBox( int nTrans ) { return new TransmissionListBox( this, nTrans ); } private static class TransmissionListBox extends JComponent { private final TransmissionListIcon icon_; private final int nTrans_; private final boolean hasRx_; private final boolean hasTx_; private Dimension minSize_; TransmissionListBox( TransmissionListIcon icon, int nTrans ) { icon_ = icon; nTrans_ = nTrans; setOpaque( true ); setBackground( Color.WHITE ); setBorder( BorderFactory.createLineBorder( Color.BLACK ) ); ListDataListener listener = new ListDataListener() { public void contentsChanged( ListDataEvent evt ) { repaint(); } public void intervalAdded( ListDataEvent evt ) { repaint(); } public void intervalRemoved( ListDataEvent evt ) { repaint(); } }; hasRx_ = icon.rxModel_ != null; hasTx_ = icon.txModel_ != null; if ( hasRx_ ) { icon.rxModel_.addListDataListener( listener ); } if ( hasTx_ ) { icon.txModel_.addListDataListener( listener ); } setPreferredSize( getSizeForCount( nTrans ) ); setMinimumSize( getSizeForCount( Math.max( nTrans, 4 ) ) ); ToolTipManager.sharedInstance().registerComponent( this ); } public Dimension getSizeForCount( int nTrans ) { int width = icon_.targetIcon_.getIconWidth(); width += ( ( hasRx_ ? 1 : 0 ) + ( hasTx_ ? 1 : 0 ) ) * nTrans_ * icon_.transIconWidth_; int height = icon_.size_; Dimension size = new Dimension( width, height ); Insets insets = getInsets(); size.width += insets.left + insets.right; size.height += insets.top + insets.bottom; return size; } public String getToolTipText( MouseEvent evt ) { Point p = evt.getPoint(); Point iconPos = getIconPosition(); p.x -= iconPos.x; p.y -= iconPos.y; Transmission trans = icon_.getTransmissionAt( p ); if ( trans != null ) { return new StringBuffer() .append( trans.getMessage().getMType() ) .append( ": " ) .append( trans.getSender().toString() ) .append( " -> " ) .append( trans.getReceiver().toString() ) .toString(); } else { return null; } } private Point getIconPosition() { Insets insets = getInsets(); return new Point( insets.left, insets.top ); } protected void paintComponent( Graphics g ) { super.paintComponent( g ); Color color = g.getColor(); Rectangle bounds = getBounds(); Insets insets = getInsets(); if ( isOpaque() ) { g.setColor( getBackground() ); g.fillRect( insets.left, insets.top, bounds.width - insets.left - insets.right, bounds.height - insets.top - insets.bottom ); } g.setColor( color ); Point p = getIconPosition(); icon_.paintIcon( this, g, p.x, p.y ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionListModel.java000066400000000000000000000073071356450004300271450ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; import javax.swing.AbstractListModel; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * ListModel implementation for containing {@link Transmission} objects. * This extends the basic ListModel contract as follows: * all ListDataEvents sent to ListDataListeners will have their * source set to the {@link Transmission} object concerned, * and will have both index values equal to each other. * * @author Mark Taylor * @since 24 Nov 2008 */ class TransmissionListModel extends AbstractListModel { private final List list_; private final ChangeListener changeListener_; private int removeDelay_; /** * Constructor. * * @param removeDelay delay in milliseconds after message completion * before transmission is removed from list */ public TransmissionListModel( int removeDelay ) { removeDelay_ = removeDelay; list_ = new ArrayList(); changeListener_ = new ChangeListener() { public void stateChanged( ChangeEvent evt ) { Object src = evt.getSource(); assert src instanceof Transmission; if ( src instanceof Transmission ) { transmissionChanged( (Transmission) src ); } } }; } /** * Called whenever a transmission which is in this list has changed * state. * * @param trans transmission */ private void transmissionChanged( final Transmission trans ) { int index = list_.indexOf( trans ); if ( index >= 0 ) { fireContentsChanged( trans, index, index ); if ( trans.isDone() && removeDelay_ >= 0 ) { long sinceDone = System.currentTimeMillis() - trans.getDoneTime(); long delay = removeDelay_ - sinceDone; if ( delay <= 0 ) { removeTransmission( trans ); } else { ActionListener remover = new ActionListener() { public void actionPerformed( ActionEvent evt ) { removeTransmission( trans ); } }; new Timer( (int) delay + 1, remover ).start(); } } } } public int getSize() { return list_.size(); } public Object getElementAt( int index ) { return list_.get( index ); } /** * Adds a transmission to this list. * * @param trans transmission to add */ public void addTransmission( Transmission trans ) { int index = list_.size(); list_.add( trans ); fireIntervalAdded( trans, index, index ); trans.addChangeListener( changeListener_ ); } /** * Removes a transmission from this list. * * @param trans transmission to remove */ public void removeTransmission( final Transmission trans ) { int index = list_.indexOf( trans ); if ( index >= 0 ) { list_.remove( index ); fireIntervalRemoved( trans, index, index ); } // Defer listener removal to avoid concurrency problems // (trying to remove a listener which generated this event). SwingUtilities.invokeLater( new Runnable() { public void run() { trans.removeChangeListener( changeListener_ ); } } ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionPanel.java000066400000000000000000000221651356450004300263070ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.io.StringWriter; import java.io.PrintWriter; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.JTextArea; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampMap; import org.astrogrid.samp.SampUtils; /** * Component which displays the details of a given Transmission object. * * @author Mark Taylor * @since 5 Dec 2008 */ public class TransmissionPanel extends JPanel { private final JTextField mtypeField_; private final JTextField idField_; private final JTextField senderField_; private final JTextField receiverField_; private final JTextField statusField_; private final JTextArea messageField_; private final JTextArea responseField_; private final ChangeListener changeListener_; private Transmission trans_; /** * Constructor. */ public TransmissionPanel() { super( new BorderLayout() ); // Panel displaying one-line metadata items. Stack metaPanel = new Stack(); metaPanel.setBorder( BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ); mtypeField_ = metaPanel.addField( "MType" ); idField_ = metaPanel.addField( "Message ID" ); senderField_ = metaPanel.addField( "Sender" ); receiverField_ = metaPanel.addField( "Receiver" ); statusField_ = metaPanel.addField( "Status" ); // Panel displaying Message content. messageField_ = new JTextArea(); messageField_.setEditable( false ); messageField_.setLineWrap( false ); JComponent messagePanel = new JPanel( new BorderLayout() ); JComponent messageHeadBox = Box.createHorizontalBox(); messageHeadBox.add( new JLabel( "Message" ) ); messageHeadBox.add( Box.createHorizontalGlue() ); messagePanel.add( messageHeadBox, BorderLayout.NORTH ); messagePanel.add( new JScrollPane( messageField_ ), BorderLayout.CENTER ); // Panel displaying Response content. responseField_ = new JTextArea(); responseField_.setEditable( false ); responseField_.setLineWrap( false ); JComponent responsePanel = new JPanel( new BorderLayout() ); JComponent responseHeadBox = Box.createHorizontalBox(); responseHeadBox.add( new JLabel( "Response" ) ); responseHeadBox.add( Box.createHorizontalGlue() ); responsePanel.add( responseHeadBox, BorderLayout.NORTH ); responsePanel.add( new JScrollPane( responseField_ ), BorderLayout.CENTER ); // Place panels. JSplitPane splitter = new JSplitPane( JSplitPane.VERTICAL_SPLIT ); splitter.setTopComponent( messagePanel ); splitter.setBottomComponent( responsePanel ); splitter.setBorder( BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ); splitter.setResizeWeight( 0.5 ); add( metaPanel, BorderLayout.NORTH ); add( splitter, BorderLayout.CENTER ); // Prepare a listener to react to changes in the displayed // transmission object. changeListener_ = new ChangeListener() { public void stateChanged( ChangeEvent evt ) { updateState(); } }; } /** * Sets the transmission object being displayed. * * @param trans transmission object to display */ public void setTransmission( Transmission trans ) { if ( trans_ != null ) { trans_.removeChangeListener( changeListener_ ); } trans_ = trans; if ( trans_ != null ) { trans_.addChangeListener( changeListener_ ); } updateState(); } /** * Returns the transmission object currently being displayed. * * @return transmission */ public Transmission getTransmission() { return trans_; } /** * Invoked whenever the displayed transmission, or its characteristics, * change. */ private void updateState() { if ( trans_ == null ) { mtypeField_.setText( null ); idField_.setText( null ); senderField_.setText( null ); receiverField_.setText( null ); statusField_.setText( null ); messageField_.setText( null ); responseField_.setText( null ); } else { Message msg = trans_.getMessage(); Response response = trans_.getResponse(); Throwable error = trans_.getError(); mtypeField_.setText( msg.getMType() ); mtypeField_.setCaretPosition( 0 ); idField_.setText( formatId( trans_ ) ); idField_.setCaretPosition( 0 ); senderField_.setText( formatClient( trans_.getSender() ) ); senderField_.setCaretPosition( 0 ); receiverField_.setText( formatClient( trans_.getReceiver() ) ); receiverField_.setCaretPosition( 0 ); statusField_.setText( trans_.getStatus().getText() ); statusField_.setCaretPosition( 0 ); messageField_.setText( SampUtils.formatObject( msg, 2 ) ); messageField_.setCaretPosition( 0 ); String responseText = response == null ? null : SampUtils.formatObject( response, 2 ); final String errorText; if ( error == null ) { errorText = null; } else { StringWriter traceWriter = new StringWriter(); error.printStackTrace( new PrintWriter( traceWriter ) ); errorText = traceWriter.toString(); } StringBuffer rbuf = new StringBuffer(); if ( responseText != null ) { rbuf.append( responseText ); } if ( errorText != null ) { if ( rbuf.length() > 0 ) { rbuf.append( "\n\n" ); } rbuf.append( errorText ); } responseField_.setText( rbuf.toString() ); responseField_.setCaretPosition( 0 ); } } /** * Formats the identifier of a transmission as a string. * * @param trans transmission * @return id string */ private static String formatId( Transmission trans ) { String msgTag = trans.getMessageTag(); String msgId = trans.getMessageId(); StringBuffer idBuf = new StringBuffer(); if ( msgTag != null ) { idBuf.append( "Tag: " ) .append( msgTag ); } if ( msgId != null ) { if ( idBuf.length() > 0 ) { idBuf.append( "; " ); } idBuf.append( "ID: " ) .append( msgId ); } return idBuf.toString(); } /** * Formats a client as a string. * * @param client client * @return string */ private static String formatClient( Client client ) { StringBuffer sbuf = new StringBuffer(); sbuf.append( client.getId() ); Metadata meta = client.getMetadata(); if ( meta != null ) { String name = meta.getName(); if ( name != null ) { sbuf.append( ' ' ) .append( '(' ) .append( name ) .append( ')' ); } } return sbuf.toString(); } /** * Component for aligning headings and text fields for metadata display. */ private class Stack extends JPanel { private final GridBagLayout layer_; private int line_; /** * Constructor. */ Stack() { layer_ = new GridBagLayout(); setLayout( layer_ ); } /** * Adds an item. * * @param heading text heading for item * @return text field associated with heading */ JTextField addField( String heading ) { GridBagConstraints cons = new GridBagConstraints(); cons.gridy = line_++; cons.weighty = 1; cons.insets = new Insets( 2, 2, 2, 2 ); cons.gridx = 0; cons.fill = GridBagConstraints.HORIZONTAL; cons.anchor = GridBagConstraints.WEST; cons.weightx = 0; JLabel label = new JLabel( heading + ":" ); JTextField field = new JTextField(); field.setEditable( false ); layer_.setConstraints( label, cons ); add( label ); cons.gridx++; cons.weightx = 1; layer_.setConstraints( field, cons ); add( field ); return field; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionTableModel.java000066400000000000000000000313201356450004300272510ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.JLabel; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; import org.astrogrid.samp.Client; /** * TableModel implementation which displays Transmission objects. * * @author Mark Taylor * @since 5 Dec 2008 */ class TransmissionTableModel implements TableModel { private final List transList_; private final List tableListenerList_; private final ChangeListener changeListener_; private final Column[] columns_; private int maxRows_; private int removeDelay_; /** Cell renderer for Transmission.Status objects. */ public static final TableCellRenderer STATUS_RENDERER = createStatusCellRenderer(); /** Cell renderer for Client objects. */ private static final TableCellRenderer CLIENT_RENDERER = createClientCellRenderer(); /** * Constructor. * * @param showSender true if a Sender column is required * @param showReceiver true if a Receiver column is required * @param removeDelay time in milliseconds after transmission resolution * that it will stay in the table - after this it will be * removed automatically * @param maxRows maximum row count for table - if not set to a finite * value, Swing can get overloaded in very high message traffic */ public TransmissionTableModel( final boolean showSender, final boolean showReceiver, int removeDelay, int maxRows ) { removeDelay_ = removeDelay; maxRows_ = maxRows; transList_ = new LinkedList(); tableListenerList_ = new ArrayList(); // Set up table columns. List colList = new ArrayList(); int charWidth = 8; int icol = 0; colList.add( new Column( "MType", String.class, new TableColumn( icol++, 30 * charWidth ) ) { Object getValue( Transmission trans ) { return trans.getMessage().getMType(); } } ); if ( showSender ) { colList.add( new Column( "Sender", Object.class, new TableColumn( icol++, 20 * charWidth, CLIENT_RENDERER, null ) ) { Object getValue( Transmission trans ) { return trans.getSender(); } } ); } if ( showReceiver ) { colList.add( new Column( "Receiver", Object.class, new TableColumn( icol++, 20 * charWidth, CLIENT_RENDERER, null ) ) { Object getValue( Transmission trans ) { return trans.getReceiver(); } } ); } colList.add( new Column( "Status", Object.class, new TableColumn( icol++, 16 * charWidth, STATUS_RENDERER, null ) ) { Object getValue( Transmission trans ) { return trans.getStatus(); } } ); columns_ = (Column[]) colList.toArray( new Column[ 0 ] ); // Set up listener to monitor changes of transmissions. changeListener_ = new ChangeListener() { public void stateChanged( ChangeEvent evt ) { Object src = evt.getSource(); assert src instanceof Transmission; if ( src instanceof Transmission ) { transmissionChanged( (Transmission) src ); } } }; } /** * Returns the transmission corresponding to a given table row. * * @param irow row index * @param transmission displayed in row irow */ public Transmission getTransmission( int irow ) { return (Transmission) transList_.get( irow ); } /** * Adds a transmission (row) to this model. It will appear at the top. * * @param trans transmission to add */ public void addTransmission( Transmission trans ) { while ( transList_.size() > maxRows_ ) { transList_.remove( maxRows_ ); fireTableChanged( new TableModelEvent( this, maxRows_, maxRows_, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE ) ); } transList_.add( 0, trans ); trans.addChangeListener( changeListener_ ); fireTableChanged( new TableModelEvent( this, 0, 0, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT ) ); } /** * Removes a transmission from this model. * * @param trans transmission to remove */ public void removeTransmission( final Transmission trans ) { int index = transList_.indexOf( trans ); if ( index >= 0 ) { transList_.remove( index ); fireTableChanged( new TableModelEvent( this, index, index, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE ) ); } // Defer listener removal to avoid concurrency problems // (trying to remove a listener which generated this event). SwingUtilities.invokeLater( new Runnable() { public void run() { trans.removeChangeListener( changeListener_ ); } } ); } public int getColumnCount() { return columns_.length; } public int getRowCount() { return transList_.size(); } public Object getValueAt( int irow, int icol ) { return columns_[ icol ].getValue( getTransmission( irow ) ); } public String getColumnName( int icol ) { return columns_[ icol ].name_; } public Class getColumnClass( int icol ) { return columns_[ icol ].clazz_; } public boolean isCellEditable( int irow, int icol ) { return false; } public void setValueAt( Object value, int irow, int icol ) { throw new UnsupportedOperationException(); } public void addTableModelListener( TableModelListener listener ) { tableListenerList_.add( listener ); } public void removeTableModelListener( TableModelListener listener ) { tableListenerList_.remove( listener ); } /** * Returns a TableColumn suitable for a given column of this table. * Can be used for more customised presentation. * * @param icol column index * @return table column */ public TableColumn getTableColumn( int icol ) { return columns_[ icol ].tcol_; } /** * Called whenever a transmission which is in this list has changed * state. * * @param trans transmission */ private void transmissionChanged( final Transmission trans ) { int index = transList_.indexOf( trans ); if ( index >= 0 ) { fireTableChanged( new TableModelEvent( this, index ) ); if ( trans.isDone() && removeDelay_ >= 0 ) { long sinceDone = System.currentTimeMillis() - trans.getDoneTime(); long delay = removeDelay_ - sinceDone; if ( delay <= 0 ) { removeTransmission( trans ); } else { ActionListener remover = new ActionListener() { public void actionPerformed( ActionEvent evt ) { removeTransmission( trans ); } }; new Timer( (int) delay + 1, remover ).start(); } } } } /** * Passes a table event to all registered listeners. * * @param evt event to forward */ private void fireTableChanged( TableModelEvent evt ) { for ( Iterator it = tableListenerList_.iterator(); it.hasNext(); ) { ((TableModelListener) it.next()).tableChanged( evt ); } } /** * Describes metadata and data for a table column. */ private abstract class Column { final String name_; final Class clazz_; final TableColumn tcol_; /** * Constructor. * * @param name column name * @param clazz column content class */ Column( String name, Class clazz, TableColumn tcol ) { name_ = name; clazz_ = clazz; tcol_ = tcol; tcol_.setHeaderValue( name ); } /** * Returns the item in this column for a given transmission. * * @param trans transmission * @return cell value */ abstract Object getValue( Transmission trans ); } /** * Template custom TableCellRenderer for subclassing. */ private static abstract class CustomTableCellRenderer extends DefaultTableCellRenderer { public Component getTableCellRendererComponent( JTable table, Object value, boolean isSel, boolean hasFocus, int irow, int icol ) { int size; try { size = (int) Math.ceil( getFont() .getMaxCharBounds( ((Graphics2D) table.getGraphics()) .getFontRenderContext() ) .getHeight() ); } catch ( NullPointerException e ) { size = 16; } Component comp = super.getTableCellRendererComponent( table, value, isSel, hasFocus, irow, icol ); if ( comp instanceof JLabel ) { configureLabel( (JLabel) comp, value, size - 2 ); } return comp; } /** * Configures a JLabel given the value to render and the * component size. * * @param label renderer component to configure * @param value object to render * @param height component height in pixels */ abstract void configureLabel( JLabel label, Object value, int height ); } /** * Returns a cell renderer for Transmission.Status objects. * * @return table cell renderer */ private static TableCellRenderer createStatusCellRenderer() { return new CustomTableCellRenderer() { void configureLabel( JLabel label, Object value, int height ) { if ( value instanceof Transmission.Status ) { Transmission.Status status = (Transmission.Status) value; label.setText( status.getText() ); label.setIcon( status.getIcon( height ) ); } } }; } /** * Returns a cell renderer for Client objects. * * @return table cell renderer */ private static TableCellRenderer createClientCellRenderer() { final IconStore iconStore = new IconStore( IconStore.createEmptyIcon( 16 ) ); return new CustomTableCellRenderer() { void configureLabel( JLabel label, Object value, int height ) { if ( value instanceof Client ) { Client client = (Client) value; label.setText( client.toString() ); label.setIcon( ClientListCellRenderer .reshapeIcon( iconStore.getIcon( client ), height ) ); } } }; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/TransmissionView.java000066400000000000000000000051271356450004300261610ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.BorderLayout; import java.awt.Dimension; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.border.BevelBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableColumnModel; /** * Displays a set of transmissions in a table model, * along with a detail panel for the selected one. * * @author Mark Taylor * @since 5 Dec 2008 */ class TransmissionView extends JPanel { /** * Constructor. * * @param transModel table model containing transmissions */ public TransmissionView( final TransmissionTableModel transModel ) { super( new BorderLayout() ); final TransmissionPanel transPanel = new TransmissionPanel(); transPanel.setBorder( BorderFactory .createBevelBorder( BevelBorder.LOWERED ) ); final JTable table = new JTable( transModel ); Dimension tableSize = table.getPreferredScrollableViewportSize(); tableSize.height = 80; table.setPreferredScrollableViewportSize( tableSize ); DefaultTableColumnModel tcolModel = new DefaultTableColumnModel(); for ( int icol = 0; icol < transModel.getColumnCount(); icol++ ) { tcolModel.addColumn( transModel.getTableColumn( icol ) ); } table.setColumnModel( tcolModel ); final ListSelectionModel selModel = table.getSelectionModel(); selModel.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); selModel.addListSelectionListener( new ListSelectionListener() { public void valueChanged( ListSelectionEvent evt ) { Object src = evt.getSource(); if ( ! selModel.getValueIsAdjusting() && ! selModel.isSelectionEmpty() ) { Transmission trans = transModel.getTransmission( table.getSelectedRow() ); transPanel.setTransmission( trans ); } } } ); JSplitPane splitter = new JSplitPane( JSplitPane.VERTICAL_SPLIT ); add( splitter, BorderLayout.CENTER ); splitter.setTopComponent( new JScrollPane( table ) ); splitter.setBottomComponent( transPanel ); Dimension splitSize = splitter.getPreferredSize(); splitSize.height = 180; splitter.setPreferredSize( splitSize ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/UniformCallActionManager.java000066400000000000000000000125211356450004300274750ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.awt.Component; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JMenu; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.HubConnector; import org.astrogrid.samp.client.ResultHandler; /** * SendActionManager subclass which works with messages of a single MType, * using the Aysnchronous Call/Response delivery pattern. * Concrete subclasses need only implement {@link #createMessage()}. * * @author Mark Taylor * @since 11 Nov 2008 */ public abstract class UniformCallActionManager extends AbstractCallActionManager { private final Component parent_; private final String mtype_; private final String sendType_; /** * Constructor. * * @param parent parent component * @param connector hub connector * @param mtype MType for messages transmitted by this object's actions * @param sendType short string identifying the kind of thing being * sent (used for action descriptions etc) */ public UniformCallActionManager( Component parent, GuiHubConnector connector, String mtype, String sendType ) { super( parent, connector, new SubscribedClientListModel( connector, mtype ) ); parent_ = parent; mtype_ = mtype; sendType_ = sendType; } /** * Generates the message which is sent to one or all clients * by this object's actions. * * @return {@link org.astrogrid.samp.Message}-like Map representing * message to transmit */ protected abstract Map createMessage() throws Exception; /** * Implemented simply by calling {@link #createMessage()}. */ protected Map createMessage( Client client ) throws Exception { return createMessage(); } protected Action createBroadcastAction() { return new BroadcastAction(); } /** * Returns a new targetted send menu with a title suitable for this object. * * @return new send menu */ public JMenu createSendMenu() { JMenu menu = super.createSendMenu( "Send " + sendType_ + " to..." ); menu.setIcon( getSendIcon() ); return menu; } public Action getSendAction( Client client ) { Action action = super.getSendAction( client ); action.putValue( Action.SHORT_DESCRIPTION, "Transmit to " + client + " using SAMP " + mtype_ ); return action; } /** * Action for sending broadcast messages. */ private class BroadcastAction extends AbstractAction { /** * Constructor. */ BroadcastAction() { putValue( NAME, "Broadcast " + sendType_ ); putValue( SHORT_DESCRIPTION, "Transmit " + sendType_ + " to all applications" + " listening using the SAMP protocol" ); putValue( SMALL_ICON, getBroadcastIcon() ); } public void actionPerformed( ActionEvent evt ) { HubConnector connector = getConnector(); Set recipientIdSet = null; Message msg = null; HubConnection connection = null; String tag = null; // Attempt to send the message. try { msg = Message.asMessage( createMessage() ); msg.check(); connection = connector.getConnection(); if ( connection != null ) { tag = createTag(); recipientIdSet = connection.callAll( tag, msg ).keySet(); } } catch ( Exception e ) { ErrorDialog.showError( parent_, "Send Error", "Send failure " + e.getMessage(), e ); } // If it was sent, arrange for the results to be passed to // a suitable result handler. if ( recipientIdSet != null ) { assert connection != null; assert msg != null; assert tag != null; List recipientList = new ArrayList(); Map clientMap = connector.getClientMap(); for ( Iterator it = recipientIdSet.iterator(); it.hasNext(); ) { String id = (String) it.next(); Client recipient = (Client) clientMap.get( id ); if ( recipient != null ) { recipientList.add( recipient ); } } Client[] recipients = (Client[]) recipientList.toArray( new Client[ 0 ] ); ResultHandler handler = createResultHandler( connection, msg, recipients ); if ( recipients.length == 0 ) { if ( handler != null ) { handler.done(); } handler = null; } registerHandler( tag, recipients, handler ); } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/WrapperHubConnection.java000066400000000000000000000052001356450004300267240ustar00rootroot00000000000000package org.astrogrid.samp.gui; import java.util.List; import java.util.Map; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * HubConnection implementation which delegates all behaviour to a base * implementation. Intended for subclassing. * * @author Mark Taylor * @since 24 Nov 2008 */ class WrapperHubConnection implements HubConnection { private final HubConnection base_; public WrapperHubConnection( HubConnection base ) { base_ = base; } public RegInfo getRegInfo() { return base_.getRegInfo(); } public void setCallable( CallableClient callable ) throws SampException { base_.setCallable( callable ); } public void ping() throws SampException { base_.ping(); } public void unregister() throws SampException { base_.unregister(); } public void declareMetadata( Map meta ) throws SampException { base_.declareMetadata( meta ); } public Metadata getMetadata( String clientId ) throws SampException { return base_.getMetadata( clientId ); } public void declareSubscriptions( Map subs ) throws SampException { base_.declareSubscriptions( subs ); } public Subscriptions getSubscriptions( String clientId ) throws SampException { return base_.getSubscriptions( clientId ); } public String[] getRegisteredClients() throws SampException { return base_.getRegisteredClients(); } public Map getSubscribedClients( String mtype ) throws SampException { return base_.getSubscribedClients( mtype ); } public void notify( String recipientId, Map msg ) throws SampException { base_.notify( recipientId, msg ); } public List notifyAll( Map msg ) throws SampException { return base_.notifyAll( msg ); } public String call( String recipientId, String msgTag, Map msg ) throws SampException { return base_.call( recipientId, msgTag, msg ); } public Map callAll( String msgTag, Map msg ) throws SampException { return base_.callAll( msgTag, msg ); } public Response callAndWait( String recipientId, Map msg, int timeout ) throws SampException { return base_.callAndWait( recipientId, msg, timeout ); } public void reply( String msgId, Map response ) throws SampException { base_.reply( msgId, response ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/gui/package.html000066400000000000000000000001251356450004300242440ustar00rootroot00000000000000 Classes required only for graphical components based on SAMP classes. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/000077500000000000000000000000001356450004300223245ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/DirectoryMapperHandler.java000066400000000000000000000034141356450004300276000ustar00rootroot00000000000000package org.astrogrid.samp.httpd; import java.net.MalformedURLException; import java.net.URL; /** * Handler implementation which allows the server to serve a directory * full of resources. * The {@link URLMapperHandler} class can sort of do the same thing, * but it's difficult to get the path right. * *

For if an instance is initialised as * new DirectoryMapperHandler("/files", "/data") * and installed on a server running at http://localhost:8000/, * then a server request for http://localhost:8000/data/xxx * would be fulfilled by returning the content of the resource * /files/xxx available to the JVM's classloader * (for instance within a jar file on the classpath). * * @author Mark Taylor * @since 11 Mar 2016 */ public class DirectoryMapperHandler implements HttpServer.Handler { private final String localDocBase_; private final String serverDocPath_; /** * Constructor. * * @param localDocBase prefix for resources available within the JVM * @param serverDocPath prefix for resources made available by this server */ public DirectoryMapperHandler( String localDocBase, String serverDocPath ) { localDocBase_ = localDocBase; serverDocPath_ = serverDocPath; } public HttpServer.Response serveRequest( HttpServer.Request request ) { String path = request.getUrl(); if ( ! path.startsWith( serverDocPath_ ) ) { return null; } String relPath = path.substring( serverDocPath_.length() ); final URL srcUrl = getClass().getResource( localDocBase_ + relPath ); return srcUrl == null ? null : URLMapperHandler.mapUrlResponse( request.getMethod(), srcUrl ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/HttpServer.java000066400000000000000000000766721356450004300253170ustar00rootroot00000000000000package org.astrogrid.samp.httpd; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.MalformedURLException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.logging.Logger; import java.util.logging.Level; import javax.net.ssl.SSLServerSocket; import org.astrogrid.samp.SampUtils; /** * Simple modular HTTP server. * One thread is started per request. Connections are not kept open between * requests. * Suitable for very large response bodies, but not for very large * request bodies. * Add one or more {@link HttpServer.Handler}s to serve actual requests. * The protocol version served is HTTP/1.0. * *

This class is completely self-contained, so that it can easily be * lifted out and used in other packages if required. * * @author Mark Taylor * @since 21 Aug 2008 */ public class HttpServer { private final ServerSocket serverSocket_; private boolean isDaemon_; private List handlerList_; private final URL baseUrl_; private volatile boolean started_; private volatile boolean stopped_; /** Header string for MIME content type. */ public static final String HDR_CONTENT_TYPE = "Content-Type"; private static final String HDR_CONTENT_LENGTH = "Content-Length"; /** Status code for OK (200). */ public static final int STATUS_OK = 200; private static final String URI_REGEX = "([^\\s\\?]*)\\??([^\\s\\?]*)"; private static final String HTTP_VERSION_REGEX = "HTTP/[0-9]+\\.[0-9]+"; private static final String HTTP_TOKEN_REGEX = "[a-zA-Z0-9_\\.\\-]+"; private static final Pattern SIMPLE_REQUEST_PATTERN = Pattern.compile( "GET" + " " + "(" + "\\S+" + ")" ); private static final Pattern REQUEST_LINE_PATTERN = Pattern.compile( "(" + HTTP_TOKEN_REGEX + ")" // typically GET, HEAD etc + " " + "(" + "\\S+" + ")" + " " + HTTP_VERSION_REGEX ); private static final Pattern HEADER_PATTERN = Pattern.compile( "(" + "[^\\s:]+" +")" + ":\\s*(.*)" ); private static final Logger logger_ = Logger.getLogger( HttpServer.class.getName() ); /** * Constructs a server based on a given socket. * * @param socket listening socket */ public HttpServer( ServerSocket socket ) { serverSocket_ = socket; isDaemon_ = true; handlerList_ = Collections.synchronizedList( new ArrayList() ); boolean isTls = socket instanceof SSLServerSocket; String scheme = isTls ? "https" : "http"; StringBuffer ubuf = new StringBuffer() .append( scheme ) .append( "://" ) .append( SampUtils.getLocalhost() ); int port = socket.getLocalPort(); if ( port != ( isTls ? 443 : 80 ) ) { ubuf.append( ':' ) .append( port ); } try { baseUrl_ = new URL( ubuf.toString() ); } catch ( MalformedURLException e ) { throw new AssertionError( "Bad scheme " + scheme + ": ??" ); } } /** * Constructs a server based on a default socket, on any free port. */ public HttpServer() throws IOException { this( new ServerSocket( 0 ) ); } /** * Adds a handler which can serve some requests going through this server. * * @param handler handler to add */ public void addHandler( Handler handler ) { handlerList_.add( handler ); } /** * Removes a handler previously added by {@link #addHandler}. * * @param handler handler to remove */ public void removeHandler( Handler handler ) { handlerList_.remove( handler ); } /** * Returns the socket on which this server listens. * * @return server socket */ public ServerSocket getSocket() { return serverSocket_; } /** * Returns the base URL for this server. * * @return base URL */ public URL getBaseUrl() { return baseUrl_; } /** * Does the work for providing output corresponding to a given HTTP request. * This implementation calls each Handler in turn and the first one * to provide a non-null response is used. * * @param request represents an HTTP request that has been received * @return represents the content of an HTTP response that should be sent */ public Response serve( Request request ) { Handler[] handlers = (Handler[]) handlerList_.toArray( new Handler[ 0 ] ); for ( int ih = 0; ih < handlers.length; ih++ ) { Handler handler = handlers[ ih ]; Response response = handler.serveRequest( request ); if ( response != null ) { return response; } } return createErrorResponse( 404, "No handler for URL" ); } /** * Determines whether the server thread will be a daemon thread or not. * Must be called before {@link #start} to have an effect. * The default is true. * * @param isDaemon whether server thread will be daemon * @see java.lang.Thread#setDaemon */ public void setDaemon( boolean isDaemon ) { isDaemon_ = isDaemon; } /** * Starts the server if it is not already started. */ public synchronized void start() { if ( ! started_ ) { Thread server = new Thread( "HTTP Server" ) { public void run() { try { while ( ! stopped_ ) { try { final Socket sock = serverSocket_.accept(); new Thread( "HTTP Request" ) { public void run() { try { serveRequest( sock ); } catch ( Throwable e ) { logger_.log( Level.WARNING, "Httpd error", e ); } } }.start(); } catch ( IOException e ) { if ( ! stopped_ ) { logger_.log( Level.WARNING, "Socket error", e ); } } } } finally { HttpServer.this.stop(); } } }; server.setDaemon( isDaemon_ ); logger_.info( "Server " + getBaseUrl() + " starting" ); server.start(); started_ = true; logger_.config( "Server " + getBaseUrl() + " started" ); } } /** * Stops the server if it is currently running. Processing of any requests * which have already been received is completed. */ public synchronized void stop() { if ( ! stopped_ ) { stopped_ = true; logger_.info( "Server " + getBaseUrl() + " stopping" ); try { serverSocket_.close(); } catch ( IOException e ) { logger_.log( Level.WARNING, "Error during server stop: " + e, e ); } } } /** * Indicates whether this server is currently running. * * @return true if running */ public boolean isRunning() { return started_ && ! stopped_; } /** * Called by the server thread for each new connection. * * @param sock client connection socket */ protected void serveRequest( Socket sock ) throws IOException { // Try to generate a request object by examining the socket's // input stream. If that fails, generate a response representing // the error. InputStream in = sock.getInputStream(); in = new BufferedInputStream( in ); Response response = null; Request request = null; try { request = parseRequest( in, sock.getRemoteSocketAddress() ); // If there was no input, make no response at all. if ( request == null ) { return; } } catch ( HttpException e ) { response = e.createResponse(); } catch ( IOException e ) { response = createErrorResponse( 400, "I/O error", e ); } catch ( Throwable e ) { response = createErrorResponse( 500, "Server error", e ); } // If we have a request (and hence no error response) process it to // obtain a response object. if ( response == null ) { assert request != null; try { response = serve( request ); } catch ( Throwable e ) { response = createErrorResponse( 500, e.toString(), e ); } } final Level level; switch ( response.getStatusCode() ) { case 200: level = Level.CONFIG; break; case 404: level = Level.INFO; break; default: level = Level.WARNING; } if ( logger_.isLoggable( level ) ) { StringBuffer sbuf = new StringBuffer(); if ( request != null ) { sbuf.append( request.getMethod() ) .append( ' ' ) .append( request.getUrl() ); } else { sbuf.append( "" ); } sbuf.append( " --> " ) .append( response.statusCode_ ) .append( ' ' ) .append( response.statusPhrase_ ); logger_.log( level, sbuf.toString() ); } // Send the response back to the client. BufferedOutputStream bos = new BufferedOutputStream( sock.getOutputStream() ); try { response.writeResponse( bos ); bos.flush(); } finally { try { bos.close(); } catch ( IOException e ) { } } } /** * Takes the input stream from a client connection and turns it into * a Request object. * As a special case, if the input stream has no content at all, * null is returned. * * @param in input stream * @param remoteAddress address of requesting client * @return parsed request, or null */ private static Request parseRequest( InputStream in, SocketAddress remoteAddress ) throws IOException { // Read the pre-body part. String[] hdrLines = readHeaderLines( in ); // No text at all? if ( hdrLines == null ) { return null; } // No header content? if ( hdrLines.length == 0 ) { throw new HttpException( 400, "Empty request" ); } // HTTP/0.9 style simple request - probably rare. Matcher simpleMatcher = SIMPLE_REQUEST_PATTERN.matcher( hdrLines[ 0 ] ); if ( simpleMatcher.matches() ) { return new Request( "GET", simpleMatcher.group( 1 ), new HashMap(), remoteAddress, null ); } // Normal HTTP/1.0 request. // First parse the request line. Matcher fullMatcher = REQUEST_LINE_PATTERN.matcher( hdrLines[ 0 ] ); if ( fullMatcher.matches() ) { String method = fullMatcher.group( 1 ); String uri = fullMatcher.group( 2 ); // Then read and parse header lines. HttpHeaderMap headerMap = new HttpHeaderMap(); int iLine = 1; boolean headerEnd = false; int contentLength = 0; for ( ; iLine < hdrLines.length; iLine++ ) { String line = hdrLines[ iLine ]; Matcher headerMatcher = HEADER_PATTERN.matcher( line ); if ( headerMatcher.matches() ) { String key = headerMatcher.group( 1 ); String value = headerMatcher.group( 2 ); // Cope with continuation lines. boolean cont = true; while ( iLine + 1 < hdrLines.length && cont ) { cont = false; String line1 = hdrLines[ iLine + 1 ]; if ( line1.length() > 0 ) { char c1 = line1.charAt( 0 ); if ( c1 == ' ' || c1 == '\t' ) { value += line1.trim(); iLine++; cont = true; } } } // Store the header. headerMap.addHeader( key, value ); // Iff we have a content-length line it means we can expect // a body later. if ( key.equalsIgnoreCase( HDR_CONTENT_LENGTH ) ) { try { contentLength = Integer.parseInt( value.trim() ); } catch ( NumberFormatException e ) { throw new HttpException( 400, "Failed to parse " + key + " header " + value ); } } } } // Read body if there is one. final byte[] body; if ( contentLength > 0 ) { body = new byte[ contentLength ]; int ib = 0; while ( ib < contentLength ) { int nb = in.read( body, ib, contentLength - ib ); if ( nb < 0 ) { throw new HttpException( 500, "Insufficient bytes for declared Content-Length: " + ib + "<" + contentLength ); } ib += nb; } assert ib == contentLength; } else { body = null; } // Decode escaped characters in the requested URI. uri = SampUtils.uriDecode( uri ); // Make sure it's a relative URI (probably not necessary // at HTTP 1.1). if ( uri.startsWith( "http://" ) ) { String path; try { URL url = new URL( uri ); path = url.getPath(); String query = url.getQuery(); if ( query != null ) { path += '?' + query; } uri = path; } catch ( MalformedURLException e ) { // never mind } } // Return the request. return new Request( method, uri, headerMap, remoteAddress, body ); } // Unrecognised. else { throw new HttpException( 400, "Bad request" ); } } /** * Reads the header lines from an HTTP client connection input stream. * All lines are read up to the first CRLF, which is when the body starts. * The input stream is left in a state ready to read the first body line. * As a special case, if the input stream has no content at all, * null is returned. * * @param is socket input stream * @return array of header lines (including initial request line); * the terminal blank line is not included, or null */ private static String[] readHeaderLines( InputStream is ) throws IOException { List lineList = new ArrayList(); StringBuffer sbuf = new StringBuffer(); boolean hasChars = false; for ( int c; ( c = is.read() ) >= 0; ) { hasChars = true; switch ( c ) { // CRLF is the correct HTTP line terminator. case '\r': if ( is.read() == '\n' ) { if ( sbuf.length() == 0 ) { return (String[]) lineList.toArray( new String[ 0 ] ); } else { lineList.add( sbuf.toString() ); sbuf.setLength( 0 ); } } else { throw new HttpException( 400, "CR w/o LF" ); } break; // HTTP 1.1 recommends that a lone LF is also tolerated as a // line terminator. case '\n': if ( sbuf.length() == 0 ) { return (String[]) lineList.toArray( new String[ 0 ] ); } else { lineList.add( sbuf.toString() ); sbuf.setLength( 0 ); } break; default: sbuf.append( (char) c ); } } // No input. if ( ! hasChars ) { return null; } // Special case: can handle HTTP/0.9 type simple requests. // Probably very rare. if ( lineList.size() == 1 ) { String line = (String) lineList.get( 0 ); if ( SIMPLE_REQUEST_PATTERN.matcher( line ).matches() ) { return new String[] { line }; } } // If it's got this far, there was no blank line. throw new HttpException( 500, "No CRLF line" ); } /** * Returns a header value from a header map. * Key value is case-insensitive. * In the (undesirable) case that multiple keys with the same * case-insensitive value exist, the values are concatenated with * comma separators, as per RFC2616 section 4.2. * * @param headerMap map * @param key header key * @return value of map entry with case-insensitive match for key */ public static String getHeader( Map headerMap, String key ) { List valueList = new ArrayList(); for ( Iterator it = headerMap.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); if ( ((String) entry.getKey()).equalsIgnoreCase( key ) ) { Object value = entry.getValue(); if ( value instanceof String ) { valueList.add( value ); } } } int nval = valueList.size(); if ( nval == 0 ) { return null; } else if ( nval == 1 ) { return (String) valueList.get( 0 ); } else { StringBuffer sbuf = new StringBuffer(); for ( Iterator vit = valueList.iterator(); vit.hasNext(); ) { sbuf.append( (String) vit.next() ); if ( vit.hasNext() ) { sbuf.append( ", " ); } } return sbuf.toString(); } } /** * Utility method to create an error response. * * @param code status code * @param phrase status phrase * @return new response object */ public static Response createErrorResponse( int code, String phrase ) { return new Response( code, phrase, new HashMap() ) { public void writeBody( OutputStream out ) { } }; } /** * Creates an HTTP response indicating that the requested method * (GET, POST, etc) is not supported. * * @param supportedMethods list of the methods which are supported * @return error response */ public static Response create405Response( String[] supportedMethods ) { Map hdrMap = new LinkedHashMap(); StringBuffer mlist = new StringBuffer(); for ( int i = 0; i < supportedMethods.length; i++ ) { if ( i > 0 ) { mlist.append( ", " ); } mlist.append( supportedMethods[ i ] ); } hdrMap.put( "Allow", mlist.toString() ); hdrMap.put( "Content-Length", "0" ); return new Response( 405, "Method not allowed", hdrMap ) { public void writeBody( OutputStream out ) { } }; } /** * Utility method to create an error response given an exception. * * @param code status code * @param phrase status phrase * @param e exception which caused the trouble * @return new response object */ public static Response createErrorResponse( int code, String phrase, final Throwable e ) { Map hdrMap = new LinkedHashMap(); hdrMap.put( HDR_CONTENT_TYPE, "text/plain" ); return new Response( code, phrase, hdrMap ) { public void writeBody( OutputStream out ) { PrintStream pout = new PrintStream( out ); e.printStackTrace( pout ); pout.flush(); } }; } /** * Represents a parsed HTTP client request. */ public static class Request { private final String method_; private final String url_; private final Map headerMap_; private final SocketAddress remoteAddress_; private final byte[] body_; /** * Constructor. * * @param method HTTP method string (GET, HEAD etc) * @param url requested URL path (should start "/") * @param headerMap map of HTTP request header key-value pairs * @param remoteAddress address of the client making the request * @param body bytes comprising request body, or null if none present */ public Request( String method, String url, Map headerMap, SocketAddress remoteAddress, byte[] body ) { method_ = method; url_ = url; headerMap_ = headerMap; remoteAddress_ = remoteAddress; body_ = body; } /** * Returns the request method string. * * @return GET, HEAD, or whatever */ public String getMethod() { return method_; } /** * Returns the request URL string. This should be a path starting * "/" (the hostname part is not present). * * @return url path */ public String getUrl() { return url_; } /** * Returns a map of key-value pairs representing HTTP request headers. * Note that for HTTP usage, header keys are case-insensitive * (RFC2616 sec 4.2); the {@link #getHeader} utility method * can be used to interrogate the returned map. * * @return header map */ public Map getHeaderMap() { return headerMap_; } /** * Returns the address of the client which made this request. * * @return requesting client's socket address */ public SocketAddress getRemoteAddress() { return remoteAddress_; } /** * Returns the body of the HTTP request if there was one. * * @return body bytes or null */ public byte[] getBody() { return body_; } public String toString() { StringBuffer sbuf = new StringBuffer() .append( method_ ) .append( ' ' ) .append( url_ ); if ( headerMap_ != null && ! headerMap_.isEmpty() ) { sbuf.append( "\n " ) .append( headerMap_ ); } if ( body_ != null && body_.length > 0 ) { sbuf.append( "\n " ) .append( "body[" ) .append( body_.length ) .append( ']' ); } return sbuf.toString(); } } /** * Represents a response to an HTTP request. */ public static abstract class Response { private final int statusCode_; private final String statusPhrase_; private final Map headerMap_; /** * Constructor. * * @param statusCode 3-digit status code * @param statusPhrase text string passed to client along * with the status code * @param headerMap map of key-value pairs representing response * header information; should normally contain * at least a content-type key */ public Response( int statusCode, String statusPhrase, Map headerMap ) { if ( Integer.toString( statusCode ).length() != 3 ) { throw new IllegalArgumentException( "Bad status " + statusCode ); } statusCode_ = statusCode; statusPhrase_ = statusPhrase; headerMap_ = headerMap; } /** * Returns the 3-digit status code. * * @return status code */ public int getStatusCode() { return statusCode_; } /** * Returns the status phrase. * * @return status phrase */ public String getStatusPhrase() { return statusPhrase_; } /** * Returns a map of key-value pairs representing HTTP response headers. * Note that for HTTP usage, header keys are case-insensitive * (RFC2616 sec 4.2); the {@link #getHeader} utility method * can be used to interrogate the returned map. * * @return header map */ public Map getHeaderMap() { return headerMap_; } /** * Implemented to generate the bytes in the body of the response. * * @param out destination stream for body bytes */ public abstract void writeBody( OutputStream out ) throws IOException; /** * Writes this response to an output stream in a way suitable for * replying to the client. * Status line and any headers are written, then {@link #writeBody} * is called. * * @param out destination stream */ public void writeResponse( OutputStream out ) throws IOException { String statusLine = new StringBuffer() .append( "HTTP/1.0" ) .append( ' ' ) .append( getStatusCode() ) .append( ' ' ) .append( getStatusPhrase() ) .append( '\r' ) .append( '\n' ) .toString(); out.write( statusLine.getBytes( "UTF-8" ) ); if ( headerMap_ != null ) { StringBuffer sbuf = new StringBuffer(); for ( Iterator it = getHeaderMap().entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); sbuf.setLength( 0 ); String line = sbuf .append( entry.getKey() ) .append( ':' ) .append( ' ' ) .append( entry.getValue() ) .append( '\r' ) .append( '\n' ) .toString(); out.write( line.getBytes( "UTF-8" ) ); } } out.write( '\r' ); out.write( '\n' ); writeBody( out ); } } /** * Convenience class for representing an error whose content should be * returned to the user as an HTTP erro response of some kind. */ private static class HttpException extends IOException { private final int code_; private final String phrase_; /** * Constructor. * * @param code 3-digit status code * @param phrase status phrase */ HttpException( int code, String phrase ) { code_ = code; phrase_ = phrase; } /** * Turns this exception into a response object. * * @return error response */ Response createResponse() { return createErrorResponse( code_, phrase_ ); } } /** * Map implementation suitable for storing HTTP headers. * It should be populated using the {@link #addHeader}, not {@link #put}, * method. * This implementation should be used when a header is being constructed * from an uncontrolled source of (key,value) pairs. * If you are adding headers yourself and know that you won't duplicate * keys, then a normal Map implementation will do. */ static class HttpHeaderMap extends LinkedHashMap { /** * Adds a header value to this map. * This differs from put in two subtle ways. * First, key matching is case-insensitive. * Second, if a value for the given key already exists, the new * value will be appended after a comma, rather than replacing * the old entry. See RFC2616 section 4.2 for the HTTP rules. * * @param key header name * @param value header value */ public void addHeader( String key, String value ) { boolean added = false; for ( Iterator it = entrySet().iterator(); it.hasNext() && ! added; ) { Map.Entry entry = (Map.Entry) it.next(); if ( ((String) entry.getKey()).equalsIgnoreCase( key ) ) { entry.setValue( entry.getValue() + ", " + value ); added = true; } } if ( ! added ) { put( key, value ); } } } /** * Implemented to serve data for some URLs. */ public interface Handler { /** * Provides a response to an HTTP request. * A handler which does not recognise the URL should simply return null; * in this case there may be another handler which is able to serve * the request. If the URL appears to be in this handler's domain but * the request cannot be served for some reason, an error response * should be returned. * * @param request HTTP request * @return response response to request, or null */ Response serveRequest( Request request ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/MultiURLMapperHandler.java000066400000000000000000000120731356450004300273120ustar00rootroot00000000000000package org.astrogrid.samp.httpd; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Handler implementation which allows the server to serve multiple * separate resources which are available to it, but not necessarily to * external clients, as URLs. The main use for this is if the URLs * are jar:-type ones (not available to clients outside the current JVM) * or file:-type ones (not available to clients on different hosts). * Only single resources, not whole trees, can be exported in this way. * *

The functionality of this class overlaps with that of * {@link URLMapperHandler}. They may be merged at some point. * * @author Mark Taylor * @since 21 Jul 2009 */ public class MultiURLMapperHandler implements HttpServer.Handler { private final HttpServer server_; private final String basePath_; private final URL baseUrl_; private final Map urlMap_; private int resourceCount_; /** * Constructor. * * @param server server within which this handler will be used * @param basePath path of served resources relative to the base * URL of the server itself */ public MultiURLMapperHandler( HttpServer server, String basePath ) throws MalformedURLException { server_ = server; if ( ! basePath.startsWith( "/" ) ) { basePath = "/" + basePath; } if ( ! basePath.endsWith( "/" ) ) { basePath = basePath + "/"; } basePath_ = basePath; baseUrl_ = new URL( server.getBaseUrl(), basePath ); urlMap_ = Collections.synchronizedMap( new HashMap() ); } /** * Returns the base URL for resources served by this handler. * * @return base URL for output */ public URL getBaseUrl() { return baseUrl_; } /** * Adds a local URL to the list of those which can be served by this * handler, and returns the public URL at which it will be available. * * @param localUrl URL readable within this JVM * @return URL readable in principle by external agents with the same * content as localUrl */ public synchronized URL addLocalUrl( URL localUrl ) { // Get a name for the publicly visible URL, using the same as the // local URL if possible. This is just for cosmetic purposes. String path = localUrl.getPath(); int lastSlash = path.lastIndexOf( "/" ); String name = lastSlash >= 0 && lastSlash < path.length() - 1 ? path.substring( lastSlash + 1 ) : "f"; // Construct a new unique public URL at which this resource will // be made available. String relPath; URL mappedUrl; try { relPath = ++resourceCount_ + "/" + name; mappedUrl = new URL( baseUrl_, relPath ); } catch ( MalformedURLException e ) { try { relPath = resourceCount_ + "/" + "f"; mappedUrl = new URL( baseUrl_, relPath ); } catch ( MalformedURLException e2 ) { throw (AssertionError) new AssertionError().initCause( e2 ); } } // Add the fragment part to the mapped URL if there is one. String frag = localUrl.getRef(); if ( frag != null ) { try { mappedUrl = new URL( mappedUrl.toString() + "#" + frag ); } catch ( MalformedURLException e ) { // shouldn't happen, but if it does, just use the non-frag one. assert false; } } // Remember the mapping between the local URL and the public one. urlMap_.put( relPath, localUrl ); // Return the public URL. return mappedUrl; } /** * Removes access to a resource which was publicised by a previous call * to {@link #addLocalUrl}. * * @param url result of previous call to addLocalUrl */ public synchronized void removeServerUrl( URL url ) { String surl = url.toString(); String sbase = baseUrl_.toString(); if ( ! surl.startsWith( sbase ) ) { return; } String relPath = surl.substring( sbase.length() ); urlMap_.remove( relPath ); } public HttpServer.Response serveRequest( HttpServer.Request request ) { // Determine the source URL from which the data will be obtained. String path = request.getUrl(); if ( ! path.startsWith( basePath_ ) ) { return null; } String relPath = path.substring( basePath_.length() ); if ( ! urlMap_.containsKey( relPath ) ) { return HttpServer.createErrorResponse( 404, "Not found" ); } URL srcUrl = (URL) urlMap_.get( relPath ); // Forward header and data from the source URL to the response. return URLMapperHandler.mapUrlResponse( request.getMethod(), srcUrl ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/ResourceHandler.java000066400000000000000000000116721356450004300262630ustar00rootroot00000000000000package org.astrogrid.samp.httpd; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Logger; /** * Handler implementation which implements dynamic resource provision. * HTTP HEAD and GET methods are implemented. * * @author Mark Taylor * @since 7 Jan 2009 */ public class ResourceHandler implements HttpServer.Handler { private final String basePath_; private final URL serverUrl_; private final Map resourceMap_; private int iRes_; private static Logger logger_ = Logger.getLogger( ResourceHandler.class.getName() ); /** Dummy resource indicating a withdrawn item. */ private static final ServerResource EXPIRED = new ServerResource() { public String getContentType() { throw new AssertionError(); } public long getContentLength() { throw new AssertionError(); } public void writeBody( OutputStream out ) { throw new AssertionError(); } }; /** * Constructor. * * @param server HTTP server * @param basePath path from server root beneath which all resources * provided by this handler will appear */ public ResourceHandler( HttpServer server, String basePath ) { if ( ! basePath.startsWith( "/" ) ) { basePath = "/" + basePath; } if ( ! basePath.endsWith( "/" ) ) { basePath = basePath + "/"; } basePath_ = basePath; serverUrl_ = server.getBaseUrl(); resourceMap_ = new HashMap(); } /** * Adds a resource to this server. * * @param name resource name, for cosmetic purposes only * @param resource resource to make available * @return URL at which resource can be found */ public synchronized URL addResource( String name, ServerResource resource ) { String path = basePath_ + Integer.toString( ++iRes_ ) + "/"; if ( name != null ) { try { path += URLEncoder.encode( name, "utf-8" ); } catch ( UnsupportedEncodingException e ) { logger_.warning( "No utf-8?? No cosmetic path name then" ); } } resourceMap_.put( path, resource ); try { URL url = new URL( serverUrl_, path ); logger_.info( "Resource added: " + url ); return new URL( serverUrl_, path ); } catch ( MalformedURLException e ) { throw new AssertionError( "Unknown protocol http??" ); } } /** * Removes a resource from this server. * * @param url URL returned by a previous addResource call */ public synchronized void removeResource( URL url ) { String path = url.getPath(); if ( resourceMap_.containsKey( path ) ) { logger_.info( "Resource expired: " + url ); resourceMap_.put( path, EXPIRED ); } else { throw new IllegalArgumentException( "Unknown URL to expire: " + url ); } } public HttpServer.Response serveRequest( HttpServer.Request request ) { String path = request.getUrl(); if ( ! path.startsWith( basePath_ ) ) { return null; } final ServerResource resource = (ServerResource) resourceMap_.get( path ); if ( resource == EXPIRED ) { return HttpServer.createErrorResponse( 410, "Gone" ); } else if ( resource != null ) { Map hdrMap = new LinkedHashMap(); hdrMap.put( "Content-Type", resource.getContentType() ); long contentLength = resource.getContentLength(); if ( contentLength >= 0 ) { hdrMap.put( "Content-Length", Long.toString( contentLength ) ); } String method = request.getMethod(); if ( method.equals( "HEAD" ) ) { return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) { } }; } else if ( method.equals( "GET" ) ) { return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) throws IOException { resource.writeBody( out ); } }; } else { return HttpServer .create405Response( new String[] { "HEAD", "GET" } ); } } else { return HttpServer.createErrorResponse( 404, "Not found" ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/ServerResource.java000066400000000000000000000014551356450004300261520ustar00rootroot00000000000000package org.astrogrid.samp.httpd; import java.io.IOException; import java.io.OutputStream; /** * Defines a resource suitable for serving by the {@link ResourceHandler} * HTTP server handler. * * @author Mark Taylor * @since 3 Sep 2008 */ public interface ServerResource { /** * Returns the MIME type of this resource. * * @return value of Content-Type HTTP header */ String getContentType(); /** * Returns the number of bytes in this resource, if known. * * @return value of Content-Length HTTP header if known; * otherwise a negative number */ long getContentLength(); /** * Writes resource body. * * @param out destination stream */ void writeBody( OutputStream out ) throws IOException; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/URLMapperHandler.java000066400000000000000000000131741356450004300263020ustar00rootroot00000000000000package org.astrogrid.samp.httpd; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.LinkedHashMap; import java.util.Map; /** * Handler implementation which allows the server to serve resources which * are available to it as URLs. The main use for this is if the URLs * are jar:-type ones which are available to the JVM in which the server * is running, but not to it's clients. * Either a single resource or a whole tree may be served. * * @author Mark Taylor * @since 8 Jan 2009 */ public class URLMapperHandler implements HttpServer.Handler { private final String basePath_; private final URL baseUrl_; private final URL sourceUrl_; private final boolean includeRelatives_; /** * Constructor. * * @param server server within which this handler will be used * @param basePath path of served resources relative to the base path * of the server itself * @param sourceUrl URL of the resource which is to be made available * at the basePath by this handler * @param includeRelatives if true, relative URLs based at * basePath * may be requested (potentially giving access to * for instance the entire tree of classpath resources); * if false, only the exact resource named by * sourceUrl is served */ public URLMapperHandler( HttpServer server, String basePath, URL sourceUrl, boolean includeRelatives ) throws MalformedURLException { if ( ! basePath.startsWith( "/" ) ) { basePath = "/" + basePath; } if ( ! basePath.endsWith( "/" ) && includeRelatives ) { basePath = basePath + "/"; } basePath_ = basePath; baseUrl_ = new URL( server.getBaseUrl(), basePath ); sourceUrl_ = sourceUrl; includeRelatives_ = includeRelatives; } /** * Returns the base URL for this handler. * If not including relatives, this will be the only URL served. * * @return URL */ public URL getBaseUrl() { return baseUrl_; } public HttpServer.Response serveRequest( HttpServer.Request request ) { // Determine the source URL from which the data will be obtained. String path = request.getUrl(); if ( ! path.startsWith( basePath_ ) ) { return null; } String relPath = path.substring( basePath_.length() ); final URL srcUrl; if ( includeRelatives_ ) { try { srcUrl = new URL( sourceUrl_, relPath ); } catch ( MalformedURLException e ) { return HttpServer .createErrorResponse( 500, "Internal server error", e ); } } else { if ( relPath.length() == 0 ) { srcUrl = sourceUrl_; } else { return HttpServer.createErrorResponse( 403, "Forbidden" ); } } // Forward header and data from the source URL to the response. return mapUrlResponse( request.getMethod(), srcUrl ); } /** * Repackages a resource from a given target URL as an HTTP response. * The data and relevant headers are copied straight through. * GET and HEAD methods are served. * * @param method HTTP method * @param targetUrl URL containing the resource to forward * @return response redirecting to the given target URL */ public static HttpServer.Response mapUrlResponse( String method, URL targetUrl ) { final URLConnection conn; try { conn = targetUrl.openConnection(); conn.connect(); } catch ( IOException e ) { return HttpServer.createErrorResponse( 404, "Not found", e ); } try { Map hdrMap = new LinkedHashMap(); String contentType = conn.getContentType(); if ( contentType != null ) { hdrMap.put( "Content-Type", contentType ); } int contentLength = conn.getContentLength(); if ( contentLength >= 0 ) { hdrMap.put( "Content-Length", Integer.toString( contentLength ) ); } String contentEncoding = conn.getContentEncoding(); if ( contentEncoding != null ) { hdrMap.put( "Content-Encoding", contentEncoding ); } if ( "GET".equals( method ) ) { return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) throws IOException { UtilServer.copy( conn.getInputStream(), out ); } }; } else if ( "HEAD".equals( method ) ) { return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) { } }; } else { return HttpServer .create405Response( new String[] { "HEAD", "GET" } ); } } catch ( Exception e ) { return HttpServer .createErrorResponse( 500, "Internal server error", e ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/UtilServer.java000066400000000000000000000220751356450004300253010ustar00rootroot00000000000000package org.astrogrid.samp.httpd; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.BindException; import java.net.MalformedURLException; import java.net.ServerSocket; import java.net.URL; import java.util.HashSet; import java.util.Set; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility class for use with HttpServer. * *

This class performs two functions. Firstly it provides a static * {@link #getInstance} method which allows its use in a singleton-like way. * The constructor is public, so singleton use is not enforced, but if * you need a server but don't need exclusive control over it, obtaining * one in this way will ensure that you don't start a new server * (which requires a new socket and other resources) if a suitable one * is already available. * *

Secondly, it provides some utility methods, * {@link #exportResource} and {@link #exportFile}, * useful for turning files or classpath resources into * publicly viewable URLs, which is sometimes useful within a SAMP * context (for instance when providing an Icon URL in metadata). * * @author Mark Taylor * @since 22 Jul 2009 */ public class UtilServer { private final HttpServer server_; private final Set baseSet_; private MultiURLMapperHandler mapperHandler_; private ResourceHandler resourceHandler_; /** * System Property key giving a preferred port number for the server. * If unset, or 0, or the chosen port is occupied, a system-chosen * value will be used. * The property name is {@value}. */ public static final String PORT_PROP = "jsamp.server.port"; /** Buffer size for copy data from input to output stream. */ private static int BUFSIZ = 16 * 1024; /** Default instance of this class. */ private static UtilServer instance_; private static final Pattern SLASH_REGEX = Pattern.compile( "(/*)(.*?)(/*)" ); private static final Pattern NUMBER_REGEX = Pattern.compile( "(.*?)([0-9]+)" ); private static final Logger logger_ = Logger.getLogger( UtilServer.class.getName() ); /** * Constructor. * Note, it may be more appropriate to use the {@link #getInstance} method. * * @param server HTTP server providing base services */ public UtilServer( HttpServer server ) throws IOException { server_ = server; baseSet_ = new HashSet(); } /** * Returns the HttpServer associated with this object. * * @return a running server instance */ public HttpServer getServer() { return server_; } /** * Returns a handler for mapping local to external URLs associated with * this server. * * @return url mapping handler */ public synchronized MultiURLMapperHandler getMapperHandler() { if ( mapperHandler_ == null ) { try { mapperHandler_ = new MultiURLMapperHandler( server_, getBasePath( "/export" ) ); } catch ( MalformedURLException e ) { throw (AssertionError) new AssertionError().initCause( e ); } server_.addHandler( mapperHandler_ ); } return mapperHandler_; } /** * Returns a handler for general purpose resource serving associated with * this server. * * @return resource serving handler */ public synchronized ResourceHandler getResourceHandler() { if ( resourceHandler_ == null ) { resourceHandler_ = new ResourceHandler( server_, getBasePath( "/docs" ) ); server_.addHandler( resourceHandler_ ); } return resourceHandler_; } /** * Exposes a resource from a given URL as a publicly visible URL. * This is typically used if the given localUrl is * only visible locally, for instance on this JVM's classpath. * * @param localUrl URL visible at least within this JVM * @return URL for external reference to the resource */ public URL exportResource( URL localUrl ) throws IOException { return getMapperHandler().addLocalUrl( localUrl ); } /** * Exposes a resource from the JVM's classpath as a publicly visible URL. * The classloader of this class is used, which may not be the right one * for the resource in question. * For that reason, this utility method is deprecated * in favour of the overloaded method that takes a URL. * * @param resource fully qualified path to a resource in the current * classpath; separators are "/" characters * @return URL for external reference to the resource * @deprecated since after 1.3.5, * use instead {@link #exportResource(java.net.URL)} */ public URL exportResource( String resource ) throws IOException { URL localUrl = UtilServer.class.getResource( resource ); if ( localUrl != null ) { return getMapperHandler().addLocalUrl( localUrl ); } else { throw new IOException( "Not found on classpath: " + resource ); } } /** * Exposes a file in the local filesystem as a publicly visible URL. * * @param file a file on a filesystem visible from the local host * @return URL for external reference to the resource */ public URL exportFile( File file ) throws IOException { if ( file.exists() ) { return getMapperHandler().addLocalUrl( file.toURL() ); } else { throw new FileNotFoundException( "No such file: " + file ); } } /** * May be used to return a unique base path for use with this class's * HttpServer. If all users of this server use this method * to get base paths for use with different handlers, nameclash * avoidance is guaranteed. * * @param txt basic text for base path * @return base path; will likely bear some resemblance to * txt, but may be adjusted to ensure uniqueness */ public synchronized String getBasePath( String txt ) { Matcher slashMatcher = SLASH_REGEX.matcher( txt ); String pre; String body; String post; if ( slashMatcher.matches() ) { pre = slashMatcher.group( 1 ); body = slashMatcher.group( 2 ); post = slashMatcher.group( 3 ); } else { assert false; pre = ""; body = txt; post = ""; } if ( baseSet_.contains( body ) ) { String stem = body; int i = 1; while ( baseSet_.contains( stem + "-" + i ) ) { i++; } body = stem + "-" + i; } baseSet_.add( body ); return pre + body + post; } /** * Returns the default instance of this class. * The first time this method is called a new daemon UtilServer * is (lazily) created, and started. Any subsequent calls will * return the same object, unless {@link #getInstance} is called. * * @return default instance of this class */ public static synchronized UtilServer getInstance() throws IOException { if ( instance_ == null ) { ServerSocket sock = null; String sPort = System.getProperty( PORT_PROP ); if ( sPort != null && sPort.length() > 0 ) { int port = Integer.parseInt( sPort ); try { sock = new ServerSocket( port ); } catch ( BindException e ) { logger_.warning( "Can't open socket on port " + port + " (" + e + ") - use another one" ); } } if ( sock == null ) { sock = new ServerSocket( 0 ); } HttpServer server = new HttpServer( sock ); server.setDaemon( true ); server.start(); instance_ = new UtilServer( server ); } return instance_; } /** * Sets the default instance of this class. * * @param server default instance to be returned by {@link #getInstance} */ public static synchronized void setInstance( UtilServer server ) { instance_ = server; } /** * Copies the content of an input stream to an output stream. * The input stream is always closed on exit; the output stream is not. * * @param in input stream * @param out output stream */ public static void copy( InputStream in, OutputStream out ) throws IOException { byte[] buf = new byte[ BUFSIZ ]; try { for ( int nb; ( nb = in.read( buf ) ) >= 0; ) { out.write( buf, 0, nb ); } out.flush(); } finally { in.close(); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/httpd/package.html000066400000000000000000000010711356450004300246040ustar00rootroot00000000000000 Standalone HTTP server. This is used by the xmlrpc.internal XML-RPC layer implementation. It may also come in useful for client applications which wish to serve resources (such as dynamically-generated data or icons as referenced by the {@link org.astrogrid.samp.Metadata#ICONURL_KEY} item) as part of SAMP operations. Other than its use by the xmlprc.internal classes however this package is not required by the JSAMP package, and it has no dependencies on other parts of it, so it may be used as a separate item if required. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/000077500000000000000000000000001356450004300217575ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/BasicClientSet.java000066400000000000000000000024021356450004300254540ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.TreeMap; /** * Basic ClientSet implementation. * * @author Mark Taylor * @since 20 Nov 2008 */ public class BasicClientSet implements ClientSet { private final Map publicIdMap_; /** * Constructor. * * @param clientIdComparator comparator for client IDs */ public BasicClientSet( Comparator clientIdComparator ) { publicIdMap_ = Collections .synchronizedMap( new TreeMap( clientIdComparator ) ); } public synchronized void add( HubClient client ) { publicIdMap_.put( client.getId(), client ); } public synchronized void remove( HubClient client ) { publicIdMap_.remove( client.getId() ); } public synchronized HubClient getFromPublicId( String publicId ) { return (HubClient) publicIdMap_.get( publicId ); } public synchronized HubClient[] getClients() { return (HubClient[]) publicIdMap_.values().toArray( new HubClient[ 0 ] ); } public synchronized boolean containsClient( HubClient client ) { return publicIdMap_.containsValue( client ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/BasicHubService.java000066400000000000000000001334601356450004300256320ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.AbstractMessageHandler; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.UtilServer; /** * HubService implementation. * * @author Mark Taylor * @since 15 Jul 2008 */ public class BasicHubService implements HubService { private final KeyGenerator keyGen_; private final ClientIdGenerator idGen_; private final Map waiterMap_; private ClientSet clientSet_; private HubClient serviceClient_; private HubConnection serviceClientConnection_; private volatile boolean started_; private volatile boolean shutdown_; private static final char ID_DELIMITER = '_'; private final Logger logger_ = Logger.getLogger( BasicHubService.class.getName() ); private final static ProfileToken INTERNAL_PROFILE = new ProfileToken() { public String getProfileName() { return "Internal"; } public MessageRestriction getMessageRestriction() { return null; } }; /** The maximum timeout for a synchronous call permitted in seconds. * Default is 43200 = 12 hours. */ public static int MAX_TIMEOUT = 12 * 60 * 60; /** The maximum number of concurrently pending synchronous calls. * Default is 100. */ public static int MAX_WAITERS = 100; /** * Constructor. * * @param random random number generator used for message tags etc */ public BasicHubService( Random random ) { // Prepare ID generators. keyGen_ = new KeyGenerator( "m:", 16, random ); idGen_ = new ClientIdGenerator( "c" ); // Prepare the data structure which keeps track of pending synchronous // calls. waiterMap_ = Collections.synchronizedMap( new HashMap() ); } public void start() { // Prepare the data structure which keeps track of registered clients. clientSet_ = createClientSet(); // Prepare and store the client object which represents the hub itself // (the one that apparently sends samp.hub.event.shutdown messages etc). serviceClient_ = createClient( "hub", INTERNAL_PROFILE ); serviceClientConnection_ = createConnection( serviceClient_ ); Metadata meta = new Metadata(); meta.setName( "Hub" ); try { meta.setIconUrl( UtilServer.getInstance() .exportResource( "/org/astrogrid/samp/images/" + "hub.png" ) .toString() ); } catch ( Throwable e ) { logger_.warning( "Can't set icon" ); } meta.put( "author.name", "Mark Taylor" ); meta.put( "author.mail", "m.b.taylor@bristol.ac.uk" ); meta.setDescriptionText( getClass().getName() ); serviceClient_.setMetadata( meta ); HubCallableClient hubCallable = new HubCallableClient( serviceClientConnection_, createHubMessageHandlers() ); serviceClient_.setCallable( hubCallable ); serviceClient_.setSubscriptions( hubCallable.getSubscriptions() ); clientSet_.add( serviceClient_ ); started_ = true; } /** * Factory method used to create the client set used by this hub service. * * @return client set */ protected ClientSet createClientSet() { return new BasicClientSet( getIdComparator() ) { public void add( HubClient client ) { assert client.getId().indexOf( ID_DELIMITER ) < 0; super.add( client ); } }; } /** * Factory method used to create all the client objects which will * be used by this hub service. * * @param publicId client public ID * @param ptoken connection source * @return hub client */ protected HubClient createClient( String publicId, ProfileToken ptoken ) { return new HubClient( publicId, ptoken ); } /** * Constructs a list of MessageHandlers to use for the client * provided by the Hub. * * @return hub message handler list */ protected AbstractMessageHandler[] createHubMessageHandlers() { return new AbstractMessageHandler[] { new PingMessageHandler(), new MetaQueryMessageHandler( getClientSet() ), }; } /** * Returns a comparator which will order client IDs. * The ordering is in creation sequence. * * @return public ID comparator */ public Comparator getIdComparator() { return idGen_.getComparator(); } /** * Returns the structure which keeps track of registered clients. * * @return client set */ public ClientSet getClientSet() { return clientSet_; } public HubConnection register( ProfileToken ptoken ) throws SampException { if ( ! started_ ) { throw new SampException( "Not started" ); } HubClient client = createClient( idGen_.next(), ptoken ); assert client.getId().indexOf( ID_DELIMITER ) < 0; clientSet_.add( client ); hubEvent( new Message( "samp.hub.event.register" ) .addParam( "id", client.getId() ) ); return createConnection( client ); } public void disconnectAll( ProfileToken profileToken ) { HubClient[] clients = clientSet_.getClients(); List profClientIdList = new ArrayList(); for ( int ic = 0; ic < clients.length; ic++ ) { HubClient client = clients[ ic ]; if ( profileToken.equals( client.getProfileToken() ) ) { profClientIdList.add( client.getId() ); } } disconnect( (String[]) profClientIdList.toArray( new String[ 0 ] ), new Message( "samp.hub.event.shutdown" ) ); } /** * Returns a new HubConnection for use by a given hub client. * The instance methods of the returned object delegate to similarly * named protected methods of this BasicHubService object. * These BasicHubService methods may therefore be overridden to * modify the behaviour of such returned connections. * * @param caller client requiring a connection * @return connection whose methods may be called by or on behalf of * caller */ protected HubConnection createConnection( final HubClient caller ) { final BasicHubService service = this; final RegInfo regInfo = new RegInfo(); regInfo.put( RegInfo.HUBID_KEY, serviceClient_.getId() ); regInfo.put( RegInfo.SELFID_KEY, caller.getId() ); return new HubConnection() { public RegInfo getRegInfo() { return regInfo; } public void ping() throws SampException { if ( ! service.isHubRunning() ) { throw new SampException( "Service is stopped" ); } } public void unregister() throws SampException { checkCaller(); service.unregister( caller ); } public void setCallable( CallableClient callable ) throws SampException { checkCaller(); service.setCallable( caller, callable ); } public void declareMetadata( Map meta ) throws SampException { checkCaller(); service.declareMetadata( caller, meta ); } public Metadata getMetadata( String clientId ) throws SampException { checkCaller(); return service.getMetadata( caller, clientId ); } public void declareSubscriptions( Map subs ) throws SampException { checkCaller(); service.declareSubscriptions( caller, subs ); } public Subscriptions getSubscriptions( String clientId ) throws SampException { checkCaller(); return service.getSubscriptions( caller, clientId ); } public String[] getRegisteredClients() throws SampException { checkCaller(); return service.getRegisteredClients( caller ); } public Map getSubscribedClients( String mtype ) throws SampException { checkCaller(); return service.getSubscribedClients( caller, mtype ); } public void notify( String recipientId, Map message ) throws SampException { checkCaller(); service.notify( caller, recipientId, message ); } public String call( String recipientId, String msgTag, Map message ) throws SampException { checkCaller(); return service.call( caller, recipientId, msgTag, message ); } public List notifyAll( Map message ) throws SampException { checkCaller(); return service.notifyAll( caller, message ); } public Map callAll( String msgTag, Map message ) throws SampException { checkCaller(); return service.callAll( caller, msgTag, message ); } public void reply( String msgId, Map response ) throws SampException { checkCaller(); service.reply( caller, msgId, response ); } public Response callAndWait( String recipientId, Map message, int timeout ) throws SampException { checkCaller(); return service.callAndWait( caller, recipientId, message, timeout ); } /** * Checks that this connection's client is able to make calls * on this connection. If it is not, for instance if it has been * unregistered, an exception will be thrown. */ private void checkCaller() throws SampException { if ( ! clientSet_.containsClient( caller ) ) { throw new SampException( "Client not registered" ); } } }; } /** * Does the work for the unregister method of conections * registered with this service. * * @param caller client to unregister * @see org.astrogrid.samp.client.HubConnection#unregister */ protected void unregister( HubClient caller ) throws SampException { clientSet_.remove( caller ); hubEvent( new Message( "samp.hub.event.unregister" ) .addParam( "id", caller.getId() ) ); } /** * Does the work for the setCallable method of connections * registered with this service. * * @param caller client * @param callable callable object * @see org.astrogrid.samp.client.HubConnection#setCallable */ protected void setCallable( HubClient caller, CallableClient callable ) throws SampException { caller.setCallable( callable ); } /** * Does the work for the declareMetadata method of connections * registered with this service. * * @param caller client * @param meta new metadata for client * @see org.astrogrid.samp.client.HubConnection#declareMetadata */ protected void declareMetadata( HubClient caller, Map meta ) throws SampException { Metadata.asMetadata( meta ).check(); caller.setMetadata( meta ); hubEvent( new Message( "samp.hub.event.metadata" ) .addParam( "id", caller.getId() ) .addParam( "metadata", meta ) ); } /** * Does the work for the getMetadata method of connections * registered with this service. * * @param caller calling client * @param clientId id of client being queried * @return metadata for client * @see org.astrogrid.samp.client.HubConnection#getMetadata */ protected Metadata getMetadata( HubClient caller, String clientId ) throws SampException { return getClient( clientId ).getMetadata(); } /** * Does the work for the declareSubscriptions method of * connections registered with this service. * * @param caller client * @param subscriptions new subscriptions for client * @see org.astrogrid.samp.client.HubConnection#declareSubscriptions */ protected void declareSubscriptions( HubClient caller, Map subscriptions ) throws SampException { if ( ! caller.isCallable() ) { throw new SampException( "Client is not callable" ); } Subscriptions subs = Subscriptions.asSubscriptions( subscriptions ); subs.check(); caller.setSubscriptions( subs ); String callerId = caller.getId(); String serviceId = serviceClient_.getId(); String mtype = "samp.hub.event.subscriptions"; HubClient[] recipients = clientSet_.getClients(); for ( int ic = 0; ic < recipients.length; ic++ ) { HubClient recipient = recipients[ ic ]; if ( recipient != serviceClient_ && canSend( serviceClient_, recipient, mtype ) && clientSet_.containsClient( recipient ) ) { Message msg = new Message( mtype ); msg.addParam( "id", callerId ); msg.addParam( "subscriptions", getSubscriptionsFor( recipient, subs ) ); try { recipient.getCallable() .receiveNotification( serviceId, msg ); } catch ( Exception e ) { logger_.log( Level.WARNING, "Notification " + caller + " -> " + recipient + " failed: " + e, e ); } } } } /** * Does the work for the getSubscriptions method of connections * registered with this service. * * @param caller calling client * @param clientId id of client being queried * @return subscriptions for client * @see org.astrogrid.samp.client.HubConnection#getSubscriptions */ protected Subscriptions getSubscriptions( HubClient caller, String clientId ) throws SampException { return getSubscriptionsFor( caller, getClient( clientId ).getSubscriptions() ); } /** * Does the work for the getRegisteredClients method of * connections registered with this service. * * @param caller calling client * @return array of registered client IDs excluding caller's * @see org.astrogrid.samp.client.HubConnection#getRegisteredClients */ protected String[] getRegisteredClients( HubClient caller ) throws SampException { HubClient[] clients = clientSet_.getClients(); List idList = new ArrayList( clients.length ); for ( int ic = 0; ic < clients.length; ic++ ) { if ( ! clients[ ic ].equals( caller ) ) { idList.add( clients[ ic ].getId() ); } } return (String[]) idList.toArray( new String[ 0 ] ); } /** * Does the work for the getSubscribedClients method of * connections registered with this service. * * @param caller calling client * @param mtype message type * @return map in which the keys are the public IDs of clients * subscribed to mtype * @see org.astrogrid.samp.client.HubConnection#getSubscribedClients */ protected Map getSubscribedClients( HubClient caller, String mtype ) throws SampException { HubClient[] clients = clientSet_.getClients(); Map subMap = new TreeMap(); for ( int ic = 0; ic < clients.length; ic++ ) { HubClient client = clients[ ic ]; if ( ! client.equals( caller ) ) { Map sub = client.getSubscriptions().getSubscription( mtype ); if ( sub != null && canSend( caller, client, mtype ) ) { subMap.put( client.getId(), sub ); } } } return subMap; } /** * Does the work for the notify method of connections * registered with this service. * * @param caller calling client * @param recipientId public ID of client to receive message * @param message message * @see org.astrogrid.samp.client.HubConnection#notify */ protected void notify( HubClient caller, String recipientId, Map message ) throws SampException { Message msg = Message.asMessage( message ); msg.check(); String mtype = msg.getMType(); HubClient recipient = getClient( recipientId ); checkSend( caller, recipient, mtype ); try { recipient.getCallable().receiveNotification( caller.getId(), msg ); } catch ( SampException e ) { throw e; } catch ( Exception e ) { throw new SampException( e.getMessage(), e ); } } /** * Does the work for the call method of connections * registered with this service. * * @param caller calling client * @param recipientId client ID of recipient * @param msgTag message tag * @param message message * @return message ID * @see org.astrogrid.samp.client.HubConnection#call */ protected String call( HubClient caller, String recipientId, String msgTag, Map message ) throws SampException { Message msg = Message.asMessage( message ); msg.check(); String mtype = msg.getMType(); HubClient recipient = getClient( recipientId ); String msgId = MessageId.encode( caller, msgTag, false ); checkSend( caller, recipient, mtype ); try { recipient.getCallable().receiveCall( caller.getId(), msgId, msg ); } catch ( SampException e ) { throw e; } catch ( Exception e ) { throw new SampException( e.getMessage(), e ); } return msgId; } /** * Does the work for the notifyAll method of connections * registered with this service. * * @param caller calling client * @param message message * @return list of public IDs for clients to which the notify will be sent * @see org.astrogrid.samp.client.HubConnection#notifyAll */ protected List notifyAll( HubClient caller, Map message ) throws SampException { Message msg = Message.asMessage( message ); msg.check(); String mtype = msg.getMType(); HubClient[] recipients = clientSet_.getClients(); List sentList = new ArrayList(); for ( int ic = 0; ic < recipients.length; ic++ ) { HubClient recipient = recipients[ ic ]; if ( recipient != caller && canSend( caller, recipient, mtype ) && clientSet_.containsClient( recipient ) ) { try { recipient.getCallable() .receiveNotification( caller.getId(), msg ); sentList.add( recipient.getId() ); } catch ( Exception e ) { logger_.log( Level.WARNING, "Notification " + caller + " -> " + recipient + " failed: " + e, e ); } } } return sentList; } /** * Does the work for the call method of connections * registered with this service. * * @param caller calling client * @param msgTag message tag * @param message message * @return publicId->msgId map for clients to which an attempt to * send the call will be made * @see org.astrogrid.samp.client.HubConnection#callAll */ protected Map callAll( HubClient caller, String msgTag, Map message ) throws SampException { Message msg = Message.asMessage( message ); msg.check(); String mtype = msg.getMType(); String msgId = MessageId.encode( caller, msgTag, false ); HubClient[] recipients = clientSet_.getClients(); Map sentMap = new HashMap(); for ( int ic = 0; ic < recipients.length; ic++ ) { HubClient recipient = recipients[ ic ]; if ( recipient != caller && canSend( caller, recipient, mtype ) && clientSet_.containsClient( recipient ) ) { try { recipient.getCallable() .receiveCall( caller.getId(), msgId, msg ); } catch ( SampException e ) { throw e; } catch ( Exception e ) { throw new SampException( e.getMessage(), e ); } sentMap.put( recipient.getId(), msgId ); } } return sentMap; } /** * Does the work for the reply method of connections * registered with this service. * * @param caller calling client * @param msgIdStr message ID * @param resp response to forward * @see org.astrogrid.samp.client.HubConnection#reply */ protected void reply( HubClient caller, String msgIdStr, Map resp ) throws SampException { Response response = Response.asResponse( resp ); response.check(); MessageId msgId = MessageId.decode( msgIdStr ); HubClient sender = getClient( msgId.getSenderId() ); String senderTag = msgId.getSenderTag(); // If we can see from the message ID that it was originally sent // synchronously, take steps to place the response in the map of // waiting messages where it will get picked up and returned to // the sender as a callAndWait return value. if ( msgId.isSynch() ) { synchronized ( waiterMap_ ) { if ( waiterMap_.containsKey( msgId ) ) { if ( waiterMap_.get( msgId ) == null ) { waiterMap_.put( msgId, response ); waiterMap_.notifyAll(); } else { throw new SampException( "Response ignored - you've already sent one" ); } } else { throw new SampException( "Response ignored - synchronous call timed out" ); } } } // Otherwise, just pass it to the sender using a callback. else { try { sender.getCallable() .receiveResponse( caller.getId(), senderTag, response ); } catch ( SampException e ) { throw e; } catch ( Exception e ) { throw new SampException( e.getMessage(), e ); } } } /** * Does the work for the callAndWait method of connections * registered with this service. * * @param caller calling client * @param recipientId client ID of recipient * @param message message * @param timeout timeout in seconds * @return response response * @see org.astrogrid.samp.client.HubConnection#callAndWait */ protected Response callAndWait( HubClient caller, String recipientId, Map message, int timeout ) throws SampException { Message msg = Message.asMessage( message ); msg.check(); String mtype = msg.getMType(); HubClient recipient = getClient( recipientId ); MessageId hubMsgId = new MessageId( caller.getId(), keyGen_.next(), true ); long start = System.currentTimeMillis(); checkSend( caller, recipient, mtype ); synchronized ( waiterMap_ ) { // If the number of pending synchronous calls exceeds the // permitted maximum, remove the oldest calls until there is // space for the new one. if ( MAX_WAITERS > 0 && waiterMap_.size() >= MAX_WAITERS ) { int excess = waiterMap_.size() - MAX_WAITERS + 1; List keyList = new ArrayList( waiterMap_.keySet() ); Collections.sort( keyList, MessageId.AGE_COMPARATOR ); logger_.warning( "Pending synchronous calls exceeds limit " + MAX_WAITERS + " - giving up on " + excess + " oldest" ); for ( int ie = 0; ie < excess; ie++ ) { Object removed = waiterMap_.remove( keyList.get( ie ) ); assert removed != null; } waiterMap_.notifyAll(); } // Place an entry for this synchronous call in the waiterMap. waiterMap_.put( hubMsgId, null ); } // Make the call asynchronously to the receiver. try { recipient.getCallable() .receiveCall( caller.getId(), hubMsgId.toString(), msg ); } catch ( SampException e ) { throw e; } catch ( Exception e ) { throw new SampException( e.getMessage(), e ); } // Wait until either the timeout expires, or the response to the // message turns up in the waiter map (placed there on another // thread by this the reply() method). timeout = Math.min( Math.max( 0, timeout ), Math.max( 0, MAX_TIMEOUT ) ); long finish = timeout > 0 ? System.currentTimeMillis() + timeout * 1000 : Long.MAX_VALUE; // 3e8 years synchronized ( waiterMap_ ) { while ( waiterMap_.containsKey( hubMsgId ) && waiterMap_.get( hubMsgId ) == null && System.currentTimeMillis() < finish ) { long millis = finish - System.currentTimeMillis(); if ( millis > 0 ) { try { waiterMap_.wait( millis ); } catch ( InterruptedException e ) { throw new SampException( "Wait interrupted", e ); } } } // If the response is there, return it to the caller of this // method (the sender of the message). if ( waiterMap_.containsKey( hubMsgId ) ) { Response response = (Response) waiterMap_.remove( hubMsgId ); if ( response != null ) { return response; } // Otherwise, it must have timed out. Exit with an error. else { assert System.currentTimeMillis() >= finish; String millis = Long.toString( System.currentTimeMillis() - start ); String emsg = new StringBuffer() .append( "Synchronous call timeout after " ) .append( millis.substring( 0, millis.length() - 3 ) ) .append( '.' ) .append( millis.substring( millis.length() - 3 ) ) .append( '/' ) .append( timeout ) .append( " sec" ) .toString(); throw new SampException( emsg ); } } else { throw new SampException( "Synchronous call aborted" + " - server load exceeded maximum of " + MAX_WAITERS + "?" ); } } } /** * Returns the HubConnection object used by the hub itself to send * and receive messages. * This is the one which apparently sends samp.hub.event.shutdown messages * etc. * * @return hub service's own hub connection */ public HubConnection getServiceConnection() { return serviceClientConnection_; } /** * Forcibly disconnects a given client. * This call does three things: *

    *
  1. sends a samp.hub.disconnect message to the * client which is about to be ejected, if the client is * subscribed to that MType
  2. *
  3. removes that client from this hub's client set so that any * further communication attempts to or from it will fail
  4. *
  5. broadcasts a samp.hub.unregister message to all * remaining clients indicating that the client has disappeared
  6. *
* * @param clientId public-id of client to eject * @param reason short text string indicating reason for ejection */ public void disconnect( String clientId, String reason ) { Message discoMsg = new Message( "samp.hub.disconnect" ); if ( reason != null && reason.length() > 0 ) { discoMsg.addParam( "reason", reason ); } disconnect( new String[] { clientId }, discoMsg ); } /** * Forcibly disconnects a number of clients for the same reason. * * @param clientIds public-ids of clients to disconnect * @param discoMsg message to send to clients which will effect * disconnection (samp.hub.disconnect or samp.hub.event.shutdown) */ private void disconnect( String[] clientIds, Message discoMsg ) { // Send the message and remove clients from client set. for ( int ic = 0; ic < clientIds.length; ic++ ) { String clientId = clientIds[ ic ]; HubClient client = clientSet_.getFromPublicId( clientId ); if ( client != null ) { if ( client.isSubscribed( discoMsg.getMType() ) ) { try { notify( serviceClient_, clientId, discoMsg ); } catch ( SampException e ) { logger_.log( Level.INFO, discoMsg.getMType() + " to " + client + " failed", e ); } } clientSet_.remove( client ); } } // Notify the remaining clients that the others have been removed. for ( int ic = 0; ic < clientIds.length; ic++ ) { hubEvent( new Message( "samp.hub.event.unregister" ) .addParam( "id", clientIds[ ic ] ) ); } } public boolean isHubRunning() { return started_ && ! shutdown_; } public synchronized void shutdown() { if ( ! shutdown_ ) { shutdown_ = true; if ( started_ ) { hubEvent( new Message( "samp.hub.event.shutdown" ) ); } serviceClientConnection_ = null; } } /** * Broadcast an event message to all subscribed clients. * The sender of this message is the hub application itself. * * @param msg message to broadcast */ private void hubEvent( Message msg ) { try { notifyAll( serviceClient_, msg ); } catch ( SampException e ) { assert false; } } /** * Returns the client object corresponding to a public client ID. * If no such client is registered, throw an exception. * * @param id client public id * @return HubClient object */ private HubClient getClient( String id ) throws SampException { HubClient client = clientSet_.getFromPublicId( id ); if ( client != null ) { return client; } else if ( idGen_.hasUsed( id ) ) { throw new SampException( "Client " + id + " is no longer registered" ); } else { throw new SampException( "No registered client with ID \"" + id + "\"" ); } } /** * Checks if a given send is permitted. Throws an exception if not. * * @param sender sending client * @param recipient receiving client * @param mtype MType * @throws SampException if the send is not permitted */ private void checkSend( HubClient sender, HubClient recipient, String mtype ) throws SampException { String errmsg = getSendError( sender, recipient, mtype ); if ( errmsg != null ) { throw new SampException( errmsg ); } } /** * Indicates whether a given send is permitted. * * @param sender sending client * @param recipient receiving client * @param mtype MType * @return true iff send OK */ private boolean canSend( HubClient sender, HubClient recipient, String mtype ) { return getSendError( sender, recipient, mtype ) == null; } /** * Does the work to determine whether a given sending client is * permitted to send a message with a given MType to a given recipient. * Returns null if allowed, a useful message if not. * Not intended for direct use, see {@link #canSend} and {@link #checkSend}. * * @param sender sending client * @param recipient receiving client * @param mtype MType * @return null if send OK, otherwise explanation message */ private String getSendError( HubClient sender, HubClient recipient, String mtype ) { if ( ! recipient.isCallable() ) { return "Client " + recipient + " is not callable"; } Subscriptions subs = recipient.getSubscriptions(); if ( ! subs.isSubscribed( mtype ) ) { return "Client " + recipient + " is not subscribed to " + mtype; } ProfileToken ptoken = sender.getProfileToken(); MessageRestriction mrestrict = ptoken.getMessageRestriction(); if ( mrestrict != null ) { Map subsInfo = subs.getSubscription( mtype ); if ( ! mrestrict.permitSend( mtype, subsInfo ) ) { return "MType " + mtype + " blocked from " + ptoken + " profile"; } } return null; } /** * Returns the view of a given subscriptions map to be presented to * a sending client. The result may be affected by any message * restrictions in force for the client. * * @param client client to view subscriptions * @param subs basic subscription map * @return view of subscription map for client */ private Subscriptions getSubscriptionsFor( HubClient client, Subscriptions subs ) { MessageRestriction mrestrict = client.getProfileToken().getMessageRestriction(); if ( mrestrict == null ) { return subs; } Subscriptions csubs = new Subscriptions(); for ( Iterator it = subs.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); String mtype = (String) entry.getKey(); Map note = (Map) entry.getValue(); if ( mrestrict.permitSend( mtype, note ) ) { csubs.put( mtype, note ); } } return csubs; } /** * Encapsulates information about a MessageId. * A message ID can be represented as a string, but encodes information * which can be retrieved later. */ private static class MessageId { private final String senderId_; private final String senderTag_; private final boolean isSynch_; private final long birthday_; private static final String T_SYNCH_FLAG = "S"; private static final String F_SYNCH_FLAG = "A"; private static final int CHECK_SEED = (int) System.currentTimeMillis(); private static final int CHECK_LENG = 4; private static final Comparator AGE_COMPARATOR = new Comparator() { public int compare( Object o1, Object o2 ) { return (int) (((MessageId) o1).birthday_ - ((MessageId) o2).birthday_); } }; /** * Constructor. * * @param senderId client id of the message sender * @param senderTag msgTag provided by the sender * @param isSynch whether the message was sent synchronously or not */ public MessageId( String senderId, String senderTag, boolean isSynch ) { senderId_ = senderId; senderTag_ = senderTag; isSynch_ = isSynch; birthday_ = System.currentTimeMillis(); } /** * Returns the sender's public client id. * * @return sender's id */ public String getSenderId() { return senderId_; } /** * Returns the msgTag attached to the message by the sender. * * @return msgTag */ public String getSenderTag() { return senderTag_; } /** * Returns whether the message was sent synchronously. * * @return true iff message was sent using callAndWait */ public boolean isSynch() { return isSynch_; } public int hashCode() { return checksum( senderId_, senderTag_, isSynch_ ).hashCode(); } public boolean equals( Object o ) { if ( o instanceof MessageId ) { MessageId other = (MessageId) o; return this.senderId_.equals( other.senderId_ ) && this.senderTag_.equals( other.senderTag_ ) && this.isSynch_ == other.isSynch_; } else { return false; } } /** * Returns the string representation of this MessageId. * * @return message ID string */ public String toString() { Object checksum = checksum( senderId_, senderTag_, isSynch_ ); return new StringBuffer() .append( senderId_ ) .append( ID_DELIMITER ) .append( isSynch_ ? T_SYNCH_FLAG : F_SYNCH_FLAG ) .append( ID_DELIMITER ) .append( checksum ) .append( ID_DELIMITER ) .append( senderTag_ ) .toString(); } /** * Decodes a msgId string to return the corresponding MessageId object. * This is the opposite of the {@link #toString} method. * * @param msgId string representation of message ID * @return new MessageId object */ public static MessageId decode( String msgId ) throws SampException { int delim1 = msgId.indexOf( ID_DELIMITER ); int delim2 = msgId.indexOf( ID_DELIMITER, delim1 + 1 ); int delim3 = msgId.indexOf( ID_DELIMITER, delim2 + 1 ); if ( delim1 < 0 || delim2 < 0 || delim3 < 0 ) { throw new SampException( "Badly formed message ID " + msgId ); } String senderId = msgId.substring( 0, delim1 ); String synchFlag = msgId.substring( delim1 + 1, delim2 ); String checksum = msgId.substring( delim2 + 1, delim3 ); String senderTag = msgId.substring( delim3 + 1 ); boolean isSynch; if ( T_SYNCH_FLAG.equals( synchFlag ) ) { isSynch = true; } else if ( F_SYNCH_FLAG.equals( synchFlag ) ) { isSynch = false; } else { throw new SampException( "Badly formed message ID " + msgId + " (synch flag)" ); } if ( ! checksum( senderId, senderTag, isSynch ) .equals( checksum ) ) { throw new SampException( "Bad message ID checksum" ); } MessageId idObj = new MessageId( senderId, senderTag, isSynch ); assert idObj.toString().equals( msgId ); return idObj; } /** * Returns a message ID string corresponding to the arguments. * * @param sender sender client * @param senderTag msgTag attached by sender * @param isSynch whether message was sent synchronously * @return string representation of message ID */ public static String encode( HubClient sender, String senderTag, boolean isSynch ) { return new MessageId( sender.getId(), senderTag, isSynch ) .toString(); } /** * Returns a checksum string which is a hash of the given arguments. * * @param senderId public client id of sender * @param senderTag msgTag attached by sender * @param isSynch whether message was sent synchronously * @return checksum string */ private static String checksum( String senderId, String senderTag, boolean isSynch ) { int sum = CHECK_SEED; sum = 23 * sum + senderId.hashCode(); sum = 23 * sum + senderTag.hashCode(); sum = 23 * sum + ( isSynch ? 3 : 5 ); String check = Integer.toHexString( sum ); check = check.substring( Math.max( 0, check.length() - CHECK_LENG ) ); while ( check.length() < CHECK_LENG ) { check = "0" + check; } assert check.length() == CHECK_LENG; return check; } } /** * Generates client public IDs. * These must be unique, but don't need to be hard to guess. */ private static class ClientIdGenerator { private int iseq_; private final String prefix_; private final Comparator comparator_; /** * Constructor. * * @param prefix prefix for all generated ids */ public ClientIdGenerator( String prefix ) { prefix_ = prefix; // Prepare a comparator which will order the keys generated here // in sequence of generation. comparator_ = new Comparator() { public int compare( Object o1, Object o2 ) { String s1 = o1.toString(); String s2 = o2.toString(); Integer i1 = getIndex( s1 ); Integer i2 = getIndex( s2 ); if ( i1 == null && i2 == null ) { return s1.compareTo( s2 ); } else if ( i1 == null ) { return +1; } else if ( i2 == null ) { return -1; } else { return i1.intValue() - i2.intValue(); } } }; } /** * Returns the next unused id. * * @return next id */ public synchronized String next() { return prefix_ + Integer.toString( ++iseq_ ); } /** * Indicates whether a given client ID has previously been dispensed * by this object. * * @param id id to test * @return true iff id has been returned by a previous call of * next */ public boolean hasUsed( String id ) { Integer ix = getIndex( id ); return ix != null && ix.intValue() <= iseq_; } /** * Returns an Integer giving the sequence index of the given id string. * If id does not look like a string generated by this * object, null is returned. * * @param id identifier to test * @return object containing sequence index of id, * or null */ private Integer getIndex( String id ) { if ( id.startsWith( prefix_ ) ) { try { int iseq = Integer.parseInt( id.substring( prefix_.length() ) ); return new Integer( iseq ); } catch ( NumberFormatException e ) { return null; } } else { return null; } } /** * Returns a comparator which will order the IDs generated by this * object in generation sequence. * * @return id comparator */ public Comparator getComparator() { return comparator_; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/ClientSet.java000066400000000000000000000021321356450004300245120ustar00rootroot00000000000000package org.astrogrid.samp.hub; /** * Data structure for keeping track of clients currently registered with a hub. * * @author Mark Taylor * @since 15 Jul 2008 */ public interface ClientSet { /** * Adds a new client to the set. * * @param client client to add */ void add( HubClient client ); /** * Removes a client from the set. * * @param client client to remove */ void remove( HubClient client ); /** * Returns the client in the set corresponding to a given public ID. * * @param publicId client public ID * @return client with id publicId if registered, or null */ HubClient getFromPublicId( String publicId ); /** * Returns an array of all the currently contained clients. * * @return client list */ HubClient[] getClients(); /** * Indicates whether a given client is currently a member of this set. * * @return true iff client is currently a member of this set */ boolean containsClient( HubClient client ); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/ConfigHubProfile.java000066400000000000000000000012451356450004300260110ustar00rootroot00000000000000package org.astrogrid.samp.hub; import javax.swing.JToggleButton; /** * Marks a HubProfile that can also provide GUI-based configuration. * This is a bit of a hack, in that it's not very general; it is just * intended at present for the WebHubProfile and is rather specific to * its needs. This interface may change or disappear at some point * in the future. * * @author Mark Taylor * @since 22 Jul 2011 */ public interface ConfigHubProfile extends HubProfile { /** * Returns some toggle button models for hub profile configuration. * * @return toggle button model array */ JToggleButton.ToggleButtonModel[] getConfigModels(); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/FacadeHubService.java000066400000000000000000000145011356450004300257460ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.astrogrid.samp.Message; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * HubService that provides hub functionality by accessing an existing * hub service. The existing hub service is defined by a supplied * ClientProfile object. * * @author Mark Taylor * @since 1 Feb 2011 */ public class FacadeHubService implements HubService { private final ClientProfile profile_; private final Map connectionMap_; // FacadeHubConnection -> ProfileToken private static final Logger logger_ = Logger.getLogger( FacadeHubService.class.getName() ); /** * Constructor. * * @param profile defines the hub connection factory on which this * service is based */ public FacadeHubService( ClientProfile profile ) { profile_ = profile; connectionMap_ = Collections.synchronizedMap( new HashMap() ); } public boolean isHubRunning() { return profile_.isHubRunning(); } public HubConnection register( ProfileToken profileToken ) throws SampException { // Mostly delegate registration to the underlying client profile, // but put in place machinery to keep track of which clients // are registered via this service. This will be required so that // they can be messaged if the underlying hub shuts down. HubConnection baseConnection = profile_.register(); if ( baseConnection != null ) { HubConnection conn = new FacadeHubConnection( baseConnection ) { final HubConnection hubConn = this; public void ping() throws SampException { if ( FacadeHubService.this.isHubRunning() ) { super.ping(); } else { throw new SampException( "Hub underlying facade " + "is not running" ); } } public void unregister() throws SampException { connectionMap_.keySet().remove( hubConn ); super.unregister(); } }; connectionMap_.put( conn, profileToken ); return conn; } // Or return null if there is no underlying hub. else { return null; } } public void disconnectAll( ProfileToken profileToken ) { Map.Entry[] entries = (Map.Entry[]) connectionMap_.entrySet() .toArray( new Map.Entry[ 0 ] ); List ejectList = new ArrayList(); for ( int ie = 0; ie < entries.length; ie++ ) { if ( profileToken.equals( entries[ ie ].getValue() ) ) { ejectList.add( entries[ ie ].getKey() ); } } FacadeHubConnection[] ejectConns = (FacadeHubConnection[]) ejectList.toArray( new FacadeHubConnection[ 0 ] ); int nc = ejectConns.length; Message discoMsg = new Message( "samp.hub.event.shutdown" ); String[] ejectIds = new String[ nc ]; for ( int ic = 0; ic < nc; ic++ ) { FacadeHubConnection conn = ejectConns[ ic ]; ejectIds[ ic ] = conn.getRegInfo().getSelfId(); conn.hubEvent( discoMsg ); connectionMap_.remove( conn ); } for ( int ic = 0; ic < nc; ic++ ) { hubEvent( new Message( "samp.hub.event.unregister" ) .addParam( "id", ejectIds[ ic ] ) ); } } /** * No-op. */ public void start() { } public void shutdown() { hubEvent( new Message( "samp.hub.event.shutdown" ) ); connectionMap_.clear(); } /** * Sends a given message by notification, as if from the hub, * to all the clients which have registered through this service. * * @param msg message to send */ private void hubEvent( Message msg ) { String mtype = msg.getMType(); FacadeHubConnection[] connections = (FacadeHubConnection[]) connectionMap_.keySet().toArray( new FacadeHubConnection[ 0 ] ); for ( int ic = 0; ic < connections.length; ic++ ) { connections[ ic ].hubEvent( msg ); } } /** * Utility HubConnection class which allows hub event notifications * to be sent to clients. */ private static class FacadeHubConnection extends WrapperHubConnection { private CallableClient callable_; private Subscriptions subs_; /** * Constructor. * * @param base base connection */ FacadeHubConnection( HubConnection base ) { super( base ); } public void setCallable( CallableClient callable ) throws SampException { super.setCallable( callable ); callable_ = callable; } public void declareSubscriptions( Map subs ) throws SampException { super.declareSubscriptions( subs ); subs_ = subs == null ? null : Subscriptions.asSubscriptions( subs ); } /** * Sends a given message as a notification, as if from the hub, * to this connection if it is able to receive it. * * @param msg message to send */ void hubEvent( Message msg ) { String mtype = msg.getMType(); CallableClient callable = callable_; if ( callable != null && subs_.isSubscribed( mtype ) ) { RegInfo regInfo = getRegInfo(); try { callable.receiveNotification( regInfo.getHubId(), msg ); } catch ( Throwable e ) { logger_.info( "Failed " + mtype + " notification to " + regInfo.getSelfId() ); } } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/Hub.java000066400000000000000000001114221356450004300233410ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFrame; import org.astrogrid.samp.ShutdownManager; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.UtilServer; import org.astrogrid.samp.web.WebHubProfile; import org.astrogrid.samp.web.WebHubProfileFactory; import org.astrogrid.samp.xmlrpc.StandardHubProfile; import org.astrogrid.samp.xmlrpc.StandardHubProfileFactory; import org.astrogrid.samp.xmlrpc.XmlRpcKit; /** * Class which manages a hub and its associated profiles. * Static methods are provided for starting a hub in the current or an * external JVM, and a main() method is provided for * use from the command line. * *

Some of the static methods allow you to indicate which hub profiles * should be used, others use a default. The default list can be set * programmatically by using the {@link #setDefaultProfileClasses} method * or externally by using the * {@value #HUBPROFILES_PROP} and {@value #EXTRAHUBPROFILES_PROP} * system properties. * So, for instance, running an application with * -Djsamp.hub.profiles=web,std will cause it to run hubs * using both the Standard and Web profiles if it does not explicitly choose * profiles. * * @author Mark Taylor * @author Sylvain Lafrasse * @since 31 Jan 2011 */ public class Hub { private final HubService service_; private final List profileList_; private static Class[] defaultDefaultProfileClasses_ = { StandardHubProfile.class, WebHubProfile.class, }; private static Class[] defaultDefaultExtraProfileClasses_ = { }; private static Class[] defaultProfileClasses_ = createDefaultProfileClasses( false ); private static Class[] defaultExtraProfileClasses_ = createDefaultProfileClasses( true ); private static final Map hubList_ = new WeakHashMap(); private static final Logger logger_ = Logger.getLogger( Hub.class.getName() ); /** * System property name for supplying default profiles ({@value}) * available at hub startup. * The value of this property, if any, will be fed to * {@link #parseProfileList}. */ public static final String HUBPROFILES_PROP = "jsamp.hub.profiles"; /** * System property name for supplying default profiles ({@value}) * additional to those in {@link #HUBPROFILES_PROP} which will be * supported by the hub but switched off at hub startup time. * The value of this property, if any, will be fed to * {@link #parseProfileList}. */ public static final String EXTRAHUBPROFILES_PROP = "jsamp.hub.profiles.extra"; /** * Constructor. * Note that this object does not start the service, it must be * started explicitly, either before or after this constructor is called. * * @param service hub service */ public Hub( HubService service ) { service_ = service; profileList_ = new ArrayList(); synchronized ( hubList_ ) { hubList_.put( this, null ); } } /** * Stops this hub and its profiles running. */ public synchronized void shutdown() { logger_.info( "Shutting down hub service" ); try { service_.shutdown(); } catch ( Throwable e ) { logger_.log( Level.WARNING, "Service shutdown error: " + e, e ); } for ( Iterator it = profileList_.iterator(); it.hasNext(); ) { HubProfile profile = (HubProfile) it.next(); logger_.info( "Shutting down hub profile " + profile.getProfileName() ); try { profile.stop(); } catch ( IOException e ) { logger_.log( Level.WARNING, "Failed to stop profile " + profile.getProfileName(), e ); } it.remove(); } synchronized ( hubList_ ) { hubList_.remove( this ); } ShutdownManager.getInstance().unregisterHook( this ); } /** * Starts a profile running on behalf of this hub. * * @param profile to start */ public synchronized void startProfile( final HubProfile profile ) throws IOException { if ( profileList_.contains( profile ) ) { logger_.info( "Profile " + profile.getProfileName() + " already started in this hub" ); } else { profile.start( new ClientProfile() { public HubConnection register() throws SampException { return service_.register( profile ); } public boolean isHubRunning() { return service_.isHubRunning(); } } ); profileList_.add( profile ); } } /** * Stops a profile running on behalf of this hub, and disconnects * all clients registered with it. * * @param profile profile to stop */ public synchronized void stopProfile( HubProfile profile ) { logger_.info( "Shutting down hub profile " + profile.getProfileName() + " and disconnecting clients" ); try { profile.stop(); } catch ( IOException e ) { logger_.log( Level.WARNING, "Failed to stop profile " + profile.getProfileName(), e ); } profileList_.remove( profile ); service_.disconnectAll( profile ); } /** * Returns the hub service associated with this hub. * * @return hub service */ public HubService getHubService() { return service_; } /** * Returns the hub profiles currently running on behalf of this hub. * * @return profiles that have been started and not yet stopped by this hub */ public HubProfile[] getRunningProfiles() { return (HubProfile[]) profileList_.toArray( new HubProfile[ 0 ] ); } /** * Returns a window for user monitoring and control of this hub, * if available. * The default implementation returns null, but this may be overridden * depending on how this hub was instantiated. * * @return hub monitor/control window, or null */ public JFrame getWindow() { return null; } /** * Returns a standard list of known HubProfileFactories. * This is used when parsing hub profile lists * ({@link #parseProfileList} to supply the well-known named profiles. * * @return array of known hub profile factories */ public static HubProfileFactory[] getKnownHubProfileFactories() { return new HubProfileFactory[] { new StandardHubProfileFactory(), new WebHubProfileFactory(), }; } /** * Returns a copy of the default set of HubProfile classes used * when a hub is run and the list of profiles is not set explicitly. * Each element should be an implementation of {@link HubProfile} * with a no-arg constructor. * * @param extra false for starting classes, true for additional ones * @return array of hub profile classes */ public static Class[] getDefaultProfileClasses( boolean extra ) { return (Class[]) ( extra ? defaultExtraProfileClasses_ : defaultProfileClasses_ ).clone(); } /** * Sets the default set of HubProfile classes. * * @param clazzes array to be returned by getDefaultProfileClasses * @param extra false for starting classes, true for additional ones */ public static void setDefaultProfileClasses( Class[] clazzes, boolean extra ) { for ( int ip = 0; ip < clazzes.length; ip++ ) { Class clazz = clazzes[ ip ]; if ( ! HubProfile.class.isAssignableFrom( clazz ) ) { throw new IllegalArgumentException( "Class " + clazz.getName() + " not a HubProfile" ); } } clazzes = (Class[]) clazzes.clone(); if ( extra ) { defaultExtraProfileClasses_ = clazzes; } else { defaultProfileClasses_ = clazzes; } } /** * Invoked at class load time to come up with the list of hub * profiles to use when no profiles are specified explicitly. * By default this is just the standard profile, but if the * {@link #HUBPROFILES_PROP} system property is defined its value * is used instead. * * @param extra false for starting classes, true for additional ones * @return default array of hub profile classes */ private static Class[] createDefaultProfileClasses( boolean extra ) { String listTxt = System.getProperty( extra ? EXTRAHUBPROFILES_PROP : HUBPROFILES_PROP ); if ( listTxt != null ) { HubProfileFactory[] facts = parseProfileList( listTxt ); Class[] clazzes = new Class[ facts.length ]; for ( int i = 0; i < facts.length; i++ ) { clazzes[ i ] = facts[ i ].getHubProfileClass(); } return clazzes; } else { return extra ? defaultDefaultExtraProfileClasses_ : defaultDefaultProfileClasses_; } } /** * Parses a string representing a list of hub profiles. * The result is an array of HubProfileFactories. * The list is comma-separated, and each element may be * either the {@link HubProfileFactory#getName name} * of a HubProfileFactory * or the classname of a {@link HubProfile} implementation * with a suitable no-arg constructor. * * @param listTxt comma-separated list * @return array of hub profile factories * @throws IllegalArgumentException if unknown */ public static HubProfileFactory[] parseProfileList( String listTxt ) { String[] txtItems = listTxt == null || listTxt.trim().length() == 0 ? new String[ 0 ] : listTxt.split( "," ); List factoryList = new ArrayList(); for ( int i = 0; i < txtItems.length; i++ ) { factoryList.add( parseProfileClass( txtItems[ i ] ) ); } return (HubProfileFactory[]) factoryList.toArray( new HubProfileFactory[ 0 ] ); } /** * Parses a string representing a hub profile. Each element may be * either the {@link HubProfileFactory#getName name} * of a HubProfileFactory * or the classname of a {@link HubProfile} implementation * with a suitable no-arg constructor. * * @param txt string * @return hub profile factory * @throws IllegalArgumentException if unknown */ private static HubProfileFactory parseProfileClass( String txt ) { HubProfileFactory[] profFacts = getKnownHubProfileFactories(); for ( int i = 0; i < profFacts.length; i++ ) { if ( txt.equals( profFacts[ i ].getName() ) ) { return profFacts[ i ]; } } final Class clazz; try { clazz = Class.forName( txt ); } catch ( ClassNotFoundException e ) { throw (IllegalArgumentException) new IllegalArgumentException( "No known hub/class " + txt ) .initCause( e ); } if ( HubProfile.class.isAssignableFrom( clazz ) ) { return new HubProfileFactory() { public Class getHubProfileClass() { return clazz; } public String[] getFlagsUsage() { return new String[ 0 ]; } public String getName() { return clazz.getName(); } public HubProfile createHubProfile( List flagList ) throws IOException { try { return (HubProfile) clazz.newInstance(); } catch ( IllegalAccessException e ) { throw (IOException) new IOException( "Can't create " + clazz.getName() + " instance" ) .initCause( e ); } catch ( InstantiationException e ) { throw (IOException) new IOException( "Can't create " + clazz.getName() + " instance" ) .initCause( e ); } catch ( ExceptionInInitializerError e ) { Throwable cause = e.getCause(); if ( cause instanceof IOException ) { throw (IOException) cause; } else { throw (IOException) new IOException( "Can't create " + clazz.getName() + " instance" ) .initCause( e ); } } } }; } else { throw new IllegalArgumentException( clazz + " is not a " + HubProfile.class.getName() ); } } /** * Returns an array of default Hub Profiles. * This is the result of calling the no-arg constructor * for each element of the result of {@link #getDefaultProfileClasses}. * * @param extra false for starting profiles, true for additional ones * @return array of hub profiles to use by default */ public static HubProfile[] createDefaultProfiles( boolean extra ) { Class[] clazzes = getDefaultProfileClasses( extra ); List hubProfileList = new ArrayList(); for ( int ip = 0; ip < clazzes.length; ip++ ) { Class clazz = clazzes[ ip ]; try { hubProfileList.add( (HubProfile) clazz.newInstance() ); } catch ( ClassCastException e ) { logger_.warning( "No hub profile " + clazz.getName() + " - not a " + HubProfile.class.getName() ); } catch ( InstantiationException e ) { logger_.warning( "No hub profile " + clazz.getName() + " - failed to instantiate (" + e + ")" ); } catch ( IllegalAccessException e ) { logger_.warning( "No hub profile " + clazz.getName() + " - inaccessible constructor (" + e + ")" ); } catch ( ExceptionInInitializerError e ) { logger_.warning( "No hub profile " + clazz.getName() + " - construction error" + " (" + e.getCause() + ")" ); } } return (HubProfile[]) hubProfileList.toArray( new HubProfile[ 0 ] ); } /** * Starts a SAMP hub with given sets of profiles. * The returned hub is running. * *

The profiles argument gives the profiles which will * be started initially, and the extraProfiles argument * lists more that can be started under user control later. * If either or both list is given as null, suitable defaults will be used. * *

If the hub mode corresponds to one of the GUI options, * one of two things will happen. An attempt will be made to install * an icon in the "system tray"; if this is successful, the attached * popup menu will provide options for displaying the hub window and * for shutting it down. If no system tray is available, the hub window * will be posted directly, and the hub will shut down when this window * is closed. System tray functionality is only available when running * under Java 1.6 or later, and when using a suitable display manager. * * @param hubMode hub mode * @param profiles SAMP profiles to support on hub startup; * if null a default set will be used * @param extraProfiles SAMP profiles to offer for later startup under * user control; if null a default set will be used * @return running hub */ public static Hub runHub( HubServiceMode hubMode, HubProfile[] profiles, HubProfile[] extraProfiles ) throws IOException { // Get values for the list of starting profiles, and the list of // additional profiles that may be started later. // If these have not been specified explicitly, use defaults. if ( profiles == null ) { profiles = createDefaultProfiles( false ); } if ( extraProfiles == null ) { extraProfiles = createDefaultProfiles( true ); } List profList = new ArrayList(); profList.addAll( Arrays.asList( profiles ) ); for ( int ip = 0; ip < extraProfiles.length; ip++ ) { HubProfile ep = extraProfiles[ ip ]; boolean gotit = false; for ( int jp = 0; jp < profiles.length; jp++ ) { gotit = gotit || profiles[ jp ].getClass().equals( ep.getClass() ); } if ( ! gotit ) { profList.add( ep ); } } HubProfile[] allProfiles = (HubProfile[]) profList.toArray( new HubProfile[ 0 ] ); // Construct a hub service ready to use the full profile list. final Hub[] runners = new Hub[ 1 ]; final HubServiceMode.ServiceGui serviceGui = hubMode.createHubService( KeyGenerator.createRandom(), allProfiles, runners ); HubService hubService = serviceGui.getHubService(); final Hub hub = new Hub( hubService ) { public JFrame getWindow() { return serviceGui.getWindow(); } }; runners[ 0 ] = hub; // Start the initial profiles. int nStarted = 0; IOException error1 = null; for ( int ip = 0; ip < profiles.length; ip++ ) { HubProfile prof = profiles[ ip ]; String pname = prof.getProfileName(); try { logger_.info( "Starting hub profile " + pname ); hub.startProfile( prof ); nStarted++; } catch ( IOException e ) { if ( error1 == null ) { error1 = e; } logger_.log( Level.WARNING, "Failed to start SAMP hub profile " + pname, e ); } } logger_.info( "Started " + nStarted + "/" + profiles.length + " SAMP profiles" ); if ( nStarted == 0 && profiles.length > 0 ) { assert error1 != null; throw (IOException) new IOException( "No SAMP profiles started: " + error1 ) .initCause( error1 ); } // Start the hub service itself. logger_.info( "Starting hub service" ); hubService.start(); ShutdownManager.getInstance() .registerHook( hub, ShutdownManager.HUB_SEQUENCE, new Runnable() { public void run() { hub.shutdown(); } } ); // Return the running hub. return hub; } /** * Starts a SAMP hub with a default set of profiles. * This convenience method invokes runHub(hubMode,null,null). * * @param hubMode hub mode * @return running hub * @see #runHub(HubServiceMode,HubProfile[],HubProfile[]) */ public static Hub runHub( HubServiceMode hubMode ) throws IOException { return runHub( hubMode, null, null ); } /** * Attempts to start a hub in a new JVM with a given set * of profiles. The resulting hub can therefore outlast the * lifetime of the current application. * Because of the OS interaction required, it's hard to make this * bulletproof, and it may fail without an exception, but we do our best. * *

The classes specified by the profileClasses and * extraProfileClasses arguments must implement * {@link HubProfile} and must have a no-arg constructor. * If null is given in either case suitable defaults, taken from the * current JVM, are used. * * @param hubMode hub mode * @param profileClasses hub profile classes to start on hub startup * @param extraProfileClasses hub profile classes which may be started * later under user control * @see #checkExternalHubAvailability */ public static void runExternalHub( HubServiceMode hubMode, Class[] profileClasses, Class[] extraProfileClasses ) throws IOException { String classpath = System.getProperty( "java.class.path" ); if ( classpath == null || classpath.trim().length() == 0 ) { throw new IOException( "No classpath available - JNLP context?" ); } File javaHome = new File( System.getProperty( "java.home" ) ); File javaExec = new File( new File( javaHome, "bin" ), "java" ); String javacmd = ( javaExec.exists() && ! javaExec.isDirectory() ) ? javaExec.toString() : "java"; String[] propagateProps = new String[] { XmlRpcKit.IMPL_PROP, UtilServer.PORT_PROP, SampUtils.LOCALHOST_PROP, HUBPROFILES_PROP, EXTRAHUBPROFILES_PROP, "java.awt.Window.locationByPlatform", }; List argList = new ArrayList(); argList.add( javacmd ); for ( int ip = 0; ip < propagateProps.length; ip++ ) { String propName = propagateProps[ ip ]; String propVal = System.getProperty( propName ); if ( propVal != null ) { argList.add( "-D" + propName + "=" + propVal ); } } argList.add( "-classpath" ); argList.add( classpath ); argList.add( Hub.class.getName() ); argList.add( "-mode" ); argList.add( hubMode.toString() ); if ( profileClasses != null ) { argList.add( "-profiles" ); StringBuffer profArg = new StringBuffer(); for ( int ip = 0; ip < profileClasses.length; ip++ ) { if ( ip > 0 ) { profArg.append( ',' ); } profArg.append( profileClasses[ ip ].getName() ); } argList.add( profArg.toString() ); } if ( extraProfileClasses != null ) { argList.add( "-extraprofiles" ); StringBuffer eprofArg = new StringBuffer(); for ( int ip = 0; ip < profileClasses.length; ip++ ) { if ( ip > 0 ) { eprofArg.append( ',' ); } eprofArg.append( extraProfileClasses[ ip ].getName() ); } argList.add( eprofArg.toString() ); } String[] args = (String[]) argList.toArray( new String[ 0 ] ); StringBuffer cmdbuf = new StringBuffer(); for ( int iarg = 0; iarg < args.length; iarg++ ) { if ( iarg > 0 ) { cmdbuf.append( ' ' ); } cmdbuf.append( args[ iarg ] ); } logger_.info( "Starting external hub" ); logger_.info( cmdbuf.toString() ); execBackground( args ); } /** * Attempts to run a hub in a new JVM with a default set of profiles. * The default set is taken from that in this JVM. * This convenience method invokes * runExternalHub(hubMode,null,null). * * @param hubMode hub mode * @see #runExternalHub(HubServiceMode,java.lang.Class[],java.lang.Class[]) */ public static void runExternalHub( HubServiceMode hubMode ) throws IOException { runExternalHub( hubMode, null, null ); } /** * Returns an array of all the instances of this class which are * currently running. * * @return running hubs */ public static Hub[] getRunningHubs() { List list; synchronized ( hubList_ ) { list = new ArrayList( hubList_.keySet() ); } for ( Iterator it = list.iterator(); it.hasNext(); ) { Hub hub = (Hub) it.next(); if ( ! hub.getHubService().isHubRunning() ) { it.remove(); } } return (Hub[]) list.toArray( new Hub[ 0 ] ); } /** * Attempts to determine whether an external hub can be started using * {@link #runExternalHub runExternalHub}. * If it can be determined that such an * attempt would fail, this method will throw an exception with * an informative message. This method succeeding is not a guarantee * that an external hub can be started successfullly. * The behaviour of this method is not expected to change over the * lifetime of a given JVM. */ public static void checkExternalHubAvailability() throws IOException { String classpath = System.getProperty( "java.class.path" ); if ( classpath == null || classpath.trim().length() == 0 ) { throw new IOException( "No classpath available - JNLP context?" ); } if ( System.getProperty( "jnlpx.jvm" ) != null ) { throw new IOException( "Running under WebStart" + " - external hub not likely to work" ); } } /** * Main method, which allows configuration of which profiles will run * and configuration of those individual profiles. * Use the -h flag for usage. */ public static void main( String[] args ) { try { int status = runMain( args ); if ( status != 0 ) { System.exit( status ); } } // Explicit exit on error may be necessary to kill Swing. catch ( Throwable e ) { e.printStackTrace(); System.exit( 2 ); } } /** * Invoked by main. * In case of a usage error, it returns a non-zero value, but does not * call System.exit. * * @param args command-line argument array * @return non-zero for error completion */ public static int runMain( String[] args ) throws IOException { HubProfileFactory[] knownProfileFactories = getKnownHubProfileFactories(); // Assemble usage message. StringBuffer pbuf = new StringBuffer(); for ( int ip = 0; ip < knownProfileFactories.length; ip++ ) { pbuf.append( knownProfileFactories[ ip ].getName() ) .append( '|' ); } pbuf.append( "" ) .append( "[,...]" ); String profUsage = pbuf.toString(); StringBuffer ubuf = new StringBuffer(); ubuf.append( "\n Usage:" ) .append( "\n " ) .append( Hub.class.getName() ) .append( "\n " ) .append( " [-help]" ) .append( " [-/+verbose]" ) .append( "\n " ) .append( " [-mode " ); HubServiceMode[] modes = HubServiceMode.getAvailableModes(); for ( int im = 0; im < modes.length; im++ ) { if ( im > 0 ) { ubuf.append( '|' ); } ubuf.append( modes[ im ].getName() ); } ubuf.append( ']' ) .append( "\n " ) .append( " [" ) .append( "-profiles " ) .append( profUsage ) .append( "]" ) .append( "\n " ) .append( " [" ) .append( "-extraprofiles " ) .append( profUsage ) .append( "]" ); for ( int ip = 0; ip < knownProfileFactories.length; ip++ ) { List pusageList = new ArrayList( Arrays.asList( knownProfileFactories[ ip ] .getFlagsUsage() ) ); while ( ! pusageList.isEmpty() ) { StringBuffer sbuf = new StringBuffer() .append( "\n " ); for ( Iterator it = pusageList.iterator(); it.hasNext(); ) { String pusage = (String) it.next(); if ( sbuf.length() + pusage.length() < 78 ) { sbuf.append( ' ' ) .append( pusage ); it.remove(); } else { break; } } ubuf.append( sbuf ); } } ubuf.append( '\n' ); String usage = ubuf.toString(); // Get default hub mode. HubServiceMode hubMode = HubServiceMode.MESSAGE_GUI; if ( ! Arrays.asList( HubServiceMode.getAvailableModes() ) .contains( hubMode ) ) { hubMode = HubServiceMode.NO_GUI; } // Parse general command-line arguments. List argList = new ArrayList( Arrays.asList( args ) ); int verbAdjust = 0; String stdSecret = null; boolean stdHttplock = false; String webAuth = "swing"; String webLog = "none"; boolean webRemote = false; String profilesTxt = null; String extraProfilesTxt = null; for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.equals( "-mode" ) && it.hasNext() ) { it.remove(); String mode = (String) it.next(); it.remove(); hubMode = HubServiceMode.getModeFromName( mode ); if ( hubMode == null ) { System.err.println( "Unkown mode " + mode ); System.err.println( usage ); return 1; } } else if ( arg.equals( "-profiles" ) ) { it.remove(); if ( it.hasNext() ) { profilesTxt = (String) it.next(); it.remove(); } else { System.err.println( usage ); return 1; } } else if ( arg.equals( "-extraprofiles" ) ) { it.remove(); if ( it.hasNext() ) { extraProfilesTxt = (String) it.next(); it.remove(); } else { System.err.println( usage ); return 1; } } else if ( arg.equals( "-v" ) || arg.equals( "-verbose" ) ) { it.remove(); verbAdjust--; } else if ( arg.equals( "+v" ) || arg.equals( "+verbose" ) ) { it.remove(); verbAdjust++; } else if ( arg.equals( "-h" ) || arg.equals( "-help" ) ) { it.remove(); System.out.println( usage ); return 0; } } // Adjust logging in accordance with verboseness flags. int logLevel = Level.WARNING.intValue() + 100 * verbAdjust; Logger.getLogger( "org.astrogrid.samp" ) .setLevel( Level.parse( Integer.toString( logLevel ) ) ); // Assemble lists of profiles to use. HubProfile[] profiles = getProfiles( profilesTxt, argList, false, "-profiles " + profUsage ); if ( profiles == null ) { return 1; } HubProfile[] extraProfiles = getProfiles( extraProfilesTxt, argList, true, "-extraprofiles " + profUsage ); if ( profiles == null ) { return 1; } // Check all command line args have been used. if ( ! argList.isEmpty() ) { System.err.println( "Some args not used " + argList ); System.err.println( usage ); return 1; } // Start hub service and install profile-specific interfaces. runHub( hubMode, profiles, extraProfiles ); // For non-GUI case block indefinitely otherwise the hub (which uses // a daemon thread) will just exit immediately. if ( hubMode.isDaemon() ) { Object lock = new String( "Indefinite" ); synchronized ( lock ) { try { lock.wait(); } catch ( InterruptedException e ) { } } } // Success return. return 0; } /** * Parses profile list command-line argument and associated * command-line arguments to construct a list of required profiles. * If there is an argument processing error, an error message will * be written to standard error and a null value will be returned. * * @param profTxt string value of profiles parameter (may be null) * @param argList complete list of other so far unused command line * args * @param isExtra true for extraProfiles, false for main ones * @param usage profile flag usage string, used for error messages * @return profiles array, or null */ private static HubProfile[] getProfiles( String profTxt, List argList, boolean isExtra, String usage ) throws IOException { if ( profTxt == null ) { Class[] dflts = isExtra ? defaultDefaultExtraProfileClasses_ : defaultDefaultProfileClasses_; if ( Arrays.equals( createDefaultProfileClasses( isExtra ), dflts ) ) { profTxt = isExtra ? "" : "std,web"; } else { logger_.warning( "Non-default profiles set external to flags; " + "web: and std: flags will be ignored" ); return createDefaultProfiles( isExtra ); } } HubProfileFactory[] pfacts; try { pfacts = parseProfileList( profTxt ); } catch ( IllegalArgumentException e ) { System.err.println( e.getMessage() ); System.err.println( usage ); return null; } HubProfile[] profiles = new HubProfile[ pfacts.length ]; for ( int i = 0; i < pfacts.length; i++ ) { HubProfileFactory pfact = pfacts[ i ]; try { profiles[ i ] = pfact.createHubProfile( argList ); } catch ( RuntimeException e ) { System.err.println( "Error configuring profile " + pfact.getName() + ":\n" + e.getMessage() ); return null; } } return profiles; } /** * Executes a command in a separate process, and discards any stdout * or stderr output generated by it. * Simply calling Runtime.exec can block the process * until its output is consumed. * * @param cmdarray array containing the command to call and its args */ private static void execBackground( String[] cmdarray ) throws IOException { Process process = Runtime.getRuntime().exec( cmdarray ); discardBytes( process.getInputStream() ); discardBytes( process.getErrorStream() ); } /** * Ensures that any bytes from a given input stream are discarded. * * @param in input stream */ private static void discardBytes( final InputStream in ) { Thread eater = new Thread( "StreamEater" ) { public void run() { try { while ( in.read() >= 0 ) {} in.close(); } catch ( IOException e ) { } } }; eater.setDaemon( true ); eater.start(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubCallableClient.java000066400000000000000000000057411356450004300261260ustar00rootroot00000000000000package org.astrogrid.samp.hub; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.AbstractMessageHandler; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * CallableClient implementation used by the hub client. * This isn't exactly essential, but it enables the hub client * (the client which represents the hub itself) to subscribe to some MTypes. * Possibly useful for testing purposes etc. * * @author Mark Taylor * @since 28 Jan 2011 */ class HubCallableClient implements CallableClient { private final HubConnection connection_; private final AbstractMessageHandler[] handlers_; /** * Constructs a HubCallableClient with a given set of handlers. * * @param connection connection to hub service * @param handlers array of message handlers */ public HubCallableClient( HubConnection connection, AbstractMessageHandler[] handlers ) { connection_ = connection; handlers_ = handlers; } public void receiveCall( String senderId, String msgId, Message msg ) throws SampException { msg.check(); getHandler( msg.getMType() ) .receiveCall( connection_, senderId, msgId, msg ); } public void receiveNotification( String senderId, Message msg ) throws SampException { msg.check(); getHandler( msg.getMType() ) .receiveNotification( connection_, senderId, msg ); } public void receiveResponse( String responderId, String msgTag, Response response ) throws SampException { } /** * Returns the subscriptions corresponding to the messages that this * receiver can deal with. * * @return subscriptions list */ public Subscriptions getSubscriptions() { Subscriptions subs = new Subscriptions(); for ( int i = 0; i < handlers_.length; i++ ) { subs.putAll( handlers_[ i ].getSubscriptions() ); } return subs; } /** * Returns a handler owned by this callable client which can handle * a given MType. If more than one applies, the first one encountered * is returned. * * @param mtype MType to handle * @return handler for mtype * @throws SampException if no suitable handler exists */ private AbstractMessageHandler getHandler( String mtype ) throws SampException { for ( int i = 0; i < handlers_.length; i++ ) { AbstractMessageHandler handler = handlers_[ i ]; if ( Subscriptions.asSubscriptions( handler.getSubscriptions() ) .isSubscribed( mtype ) ) { return handler; } } throw new SampException( "Not subscribed to " + mtype ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubClient.java000066400000000000000000000107111356450004300244770ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.util.Map; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.SampException; /** * Represents a client registered with a hub. * * @author Mark Taylor * @since 15 Jul 2008 */ public class HubClient implements Client { private final String publicId_; private final ProfileToken profileToken_; private volatile Subscriptions subscriptions_; private volatile Metadata metadata_; private volatile CallableClient callable_; /** * Constructor. * * @param publicId client public ID * @param profileToken identifier for the source of the hub connection */ public HubClient( String publicId, ProfileToken profileToken ) { publicId_ = publicId; profileToken_ = profileToken; subscriptions_ = new Subscriptions(); metadata_ = new Metadata(); callable_ = new NoCallableClient(); } public String getId() { return publicId_; } public Metadata getMetadata() { return metadata_; } public Subscriptions getSubscriptions() { return subscriptions_; } /** * Returns a token identifying the source of this client's connection * to the hub. * * @return profile token */ public ProfileToken getProfileToken() { return profileToken_; } /** * Sets this client's metadata map. * * @param meta metadata map */ public void setMetadata( Map meta ) { metadata_ = new Metadata( meta ); } /** * Sets this client's subscriptions list. * * @param subs subscriptions map */ public void setSubscriptions( Map subs ) { subscriptions_ = Subscriptions.asSubscriptions( subs ); } /** * Indicates whether this client is subscribed to a given MType. * * @param mtype MType * @return true iff subscribed to MType */ public boolean isSubscribed( String mtype ) { return isCallable() && subscriptions_.isSubscribed( mtype ); } /** * Returns the subscription information for a given MType for this client. * * @param mtype MType * @return subscriptions map value for key mtype, * or null if not subscribed */ public Map getSubscription( String mtype ) { return isCallable() ? subscriptions_.getSubscription( mtype ) : null; } /** * Sets the callable object which allows this client to receive * callbacks. If null is used, a no-op callable object is installed. * * @param callable new callable interface, or null */ public void setCallable( CallableClient callable ) { callable_ = callable == null ? new NoCallableClient() : callable; } /** * Returns the callable object which allows this client to receive * callbacks. It is never null. * * @return callable object */ public CallableClient getCallable() { return callable_; } /** * Indicates whether this client is callable. * * @return true iff this client has a non-useless callback handler * installed */ public boolean isCallable() { return ! ( callable_ instanceof NoCallableClient ); } public String toString() { return SampUtils.toString( this ); } /** * No-op callback handler implementation. * Any attempt to call its methods results in an exception. */ private class NoCallableClient implements CallableClient { public void receiveNotification( String senderId, Message message ) throws SampException { refuse(); } public void receiveCall( String senderId, String msgId, Message message ) throws SampException { refuse(); } public void receiveResponse( String responderId, String msgId, Response response ) throws SampException { refuse(); } private void refuse() throws SampException { throw new SampException( "Client " + getId() + " is not callable" ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubProfile.java000066400000000000000000000032241356450004300246620ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.io.IOException; import java.util.List; import org.astrogrid.samp.client.ClientProfile; /** * Defines a hub profile. * This profile allows registration and deregistration of clients to * a given provider of hub connections, using some profile-specific * transport and authentication arrangements. * Multiple profiles may be attached to a single connection supplier * at any time, and may be started and stopped independently of each other. * The connection supplier is typically a hub service running in the same * JVM, but may also be a client-side connection to a hub. * A profile should be able to undergo multiple start/stop cycles. * * @author Mark Taylor * @since 31 Jan 2011 */ public interface HubProfile extends ProfileToken { /** * Starts this profile's activity allowing access to a given supplier of * hub connections. * * @param profile object which can provide hub connections */ void start( ClientProfile profile ) throws IOException; /** * Indicates whether this profile is currently running. * * @return true iff profile is running */ boolean isRunning(); /** * Ends this profile's activity on behalf of the hub. * Any resources associated with the profile should be released. * This does not include messaging registered clients about profile * termination; that should be taken care of by the user of this profile. */ void stop() throws IOException; /** * Returns the name of this profile. * * @return profile name, usually one word */ public String getProfileName(); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubProfileFactory.java000066400000000000000000000035621356450004300262170ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.io.IOException; import java.util.List; /** * Factory to produce hub profiles of a particular type. * Used with the command-line invocation of the hub. * * @author Mark Taylor * @since 31 Jan 2011 */ public interface HubProfileFactory { /** * Returns the name used to identify this profile. * * @return short name */ String getName(); /** * Returns an array of strings, each describing one command-line flag * which will be consumed by the createProfile method. * * @return array of plain-text strings suitable for use as part of * a usage message */ String[] getFlagsUsage(); /** * Creates a HubProfile perhaps configured using a supplied list * of flags. Any flags which match those described by the * {@link #getFlagsUsage} command are used for configuration of the * returned hub, and must be removed from the flagList list. * Unrecognised flags should be ignored and left in the list. * Flags which are recognised but badly formed should raise a * RuntimeException with a helpful message. * * @param flagList mutable list of Strings giving command-ilne flags, * some of which may be intended for configuring a profile * @return new profile */ HubProfile createHubProfile( List flagList ) throws IOException; /** * Returns a HubProfile subclass with a no-arg constructor which, * when invoked, will produce a basic instance of the HubProfile * represented by this factory. The instance thus produced will * typically be similar to that produced by invoking * {@link #createHubProfile} with an empty flag list. * * @return HubProfile subclass with a public no-arg constructor */ Class getHubProfileClass(); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubService.java000066400000000000000000000042331356450004300246630ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.util.List; import java.util.Map; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * Interface defining the work that the hub has to do. * This is independent of profile or transport, and just concerns * keeping track of clients and routing messages between them. * * @author Mark Taylor * @since 15 Jul 2008 */ public interface HubService { /** * Begin operation. The {@link #register} method should not be * called until the hub has been started. */ void start(); /** * Creates a new connection to this hub service, thereby initiating * a new registered client. * *

It is the responsibility of the returned connection, not the * user of that connection, to broadcast the various * samp.hub.event.* notifications at the appropriate times. * *

Most of the HubConnection methods are declared to * throw SampException, however, implementations may * throw unchecked exceptions if that is more convenient; * users of the connection should be prepared to catch these if * they occur. * * @param profileToken identifier for the profile acting as gatekeeper * for this connection * @return new hub connection representing registration of a new client */ HubConnection register( ProfileToken profileToken ) throws SampException; /** * Forcibly terminates any connections created by a previous call of * {@link #register} * with a particular profileToken. * Any necessary hub events will be sent. * * @param profileToken previous argument to register */ void disconnectAll( ProfileToken profileToken ); /** * Indicates whether this hub service is currently open for operations. * * @return true iff called between {@link #start} and {@link #shutdown} */ boolean isHubRunning(); /** * Tidies up any resources owned by this object. * Should be called when no longer required. */ void shutdown(); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/HubServiceMode.java000066400000000000000000000676201356450004300255010ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.awt.AWTException; import java.awt.CheckboxMenuItem; import java.awt.Image; import java.awt.Menu; import java.awt.MenuItem; import java.awt.PopupMenu; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JFrame; import javax.swing.JToggleButton; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.gui.ErrorDialog; import org.astrogrid.samp.gui.GuiHubService; import org.astrogrid.samp.gui.MessageTrackerHubService; import org.astrogrid.samp.gui.SysTray; /** * Specifies a particular hub implementation for use with {@link Hub}. * * @author Mark Taylor * @since 20 Nov 2008 */ public abstract class HubServiceMode { // This class looks like an enumeration-type class to external users. // It is actually a HubService factory. private final String name_; private final boolean isDaemon_; private static final Logger logger_ = Logger.getLogger( HubServiceMode.class.getName() ); /** Hub mode with no GUI representation of hub operations. */ public static final HubServiceMode NO_GUI; /** Hub mode with a GUI representation of connected clients. */ public static final HubServiceMode CLIENT_GUI; /** Hub mode with a GUI representation of clients and messages. */ public static HubServiceMode MESSAGE_GUI; /** Hub Mode which piggy-backs on an existing hub using * the default client profile. */ public static HubServiceMode FACADE; /** Array of available hub modes. */ private static final HubServiceMode[] KNOWN_MODES = new HubServiceMode[] { NO_GUI = createBasicHubMode( "no-gui" ), CLIENT_GUI = createGuiHubMode( "client-gui" ), MESSAGE_GUI = createMessageTrackerHubMode( "msg-gui" ), FACADE = createFacadeHubMode( "facade" ), }; /** * Constructor. * * @param name mode name * @param isDaemon true if the hub will start only daemon threads */ HubServiceMode( String name, boolean isDaemon ) { name_ = name; isDaemon_ = isDaemon; } /** * Creates and returns a new hub service object. * * @param random random number generator * @param profiles hub profiles * @param runners 1-element array of Hubs - this should be * populated with the runner once it has been constructed * @return object containing the hub service and possibly a window */ abstract ServiceGui createHubService( Random random, HubProfile[] profiles, Hub[] runners ); /** * Indicates whether the hub service will start only daemon threads. * If it returns true, the caller may need to make sure that the * JVM doesn't stop too early. * * @return true iff no non-daemon threads will be started by the service */ boolean isDaemon() { return isDaemon_; } /** * Returns this mode's name. * * @return mode name */ String getName() { return name_; } public String toString() { return name_; } /** * Returns one of the known modes which has a name as given. * * @param name mode name (case-insensitive) * @return mode with given name, or null if none known */ public static HubServiceMode getModeFromName( String name ) { HubServiceMode[] modes = KNOWN_MODES; for ( int im = 0; im < modes.length; im++ ) { HubServiceMode mode = modes[ im ]; if ( mode.name_.equalsIgnoreCase( name ) ) { return mode; } } return null; } /** * Returns an array of the hub modes which can actually be used. * * @return available mode list */ public static HubServiceMode[] getAvailableModes() { List modeList = new ArrayList(); for ( int i = 0; i < KNOWN_MODES.length; i++ ) { HubServiceMode mode = KNOWN_MODES[ i ]; if ( ! ( mode instanceof BrokenHubMode ) ) { modeList.add( mode ); } } return (HubServiceMode[]) modeList.toArray( new HubServiceMode[ 0 ] ); } /** * Used to perform common configuration of hub display windows * for GUI-type hub modes. * * @param frame hub window * @param profiles profiles to run for hub * @param runners 1-element array which will contain an associated * hub runner object if one exists * @param hubService object providing hub services * @return object which should be shutdown when the hub stops running */ private static Tidier configureHubWindow( JFrame frame, HubProfile[] profiles, Hub[] runners, GuiHubService hubService ) { SysTray sysTray = SysTray.getInstance(); if ( sysTray.isSupported() ) { try { SysTrayWindowConfig winConfig = new SysTrayWindowConfig( frame, profiles, runners, hubService, sysTray ); winConfig.configureWindow(); winConfig.configureSysTray(); logger_.info( "Hub started in system tray" ); return winConfig; } catch ( AWTException e ) { logger_.warning( "Failed to install in system tray: " + e ); BasicWindowConfig winConfig = new BasicWindowConfig( frame, profiles, runners, hubService ); winConfig.configureWindow(); return winConfig; } } else { logger_.info( "System tray not supported: displaying hub window" ); BasicWindowConfig winConfig = new BasicWindowConfig( frame, profiles, runners, hubService ); winConfig.configureWindow(); return winConfig; } } /** * Constructs a mode for BasicHubService. * * @param name mode name * @return non-gui mode */ private static HubServiceMode createBasicHubMode( String name ) { try { return new HubServiceMode( name, true ) { ServiceGui createHubService( Random random, HubProfile[] profiles, Hub[] runners ) { ServiceGui serviceGui = new ServiceGui(); serviceGui.service_ = new BasicHubService( random ); return serviceGui; } }; } catch ( Throwable e ) { return new BrokenHubMode( name, e ); } } /** * Constructs a mode for GuiHubService. * * @return mode without message tracking */ private static HubServiceMode createGuiHubMode( String name ) { try { /* Check GuiHubService class is present; if GUI classes are * missing in a stripped-down installation find out now * (mode creation time) rather than service creation time. */ GuiHubService.class.getName(); /* Create and return the service. */ return new HubServiceMode( name, false ) { ServiceGui createHubService( Random random, final HubProfile[] profiles, final Hub[] runners ) { final ServiceGui serviceGui = new ServiceGui(); serviceGui.service_ = new GuiHubService( random ) { volatile Runnable tidierCallback; public void start() { final GuiHubService service = this; super.start(); SwingUtilities.invokeLater( new Runnable() { public void run() { JFrame window = createHubWindow(); final Tidier tidier = configureHubWindow( window, profiles, runners, service ); tidierCallback = new Runnable() { public void run() { tidier.tidyGui(); } }; serviceGui.window_ = window; } } ); } public void shutdown() { super.shutdown(); /* It is (apparently) necessary under some * circumstances to call an existing Runnable here * rather than creating a new one because of * weird (buggy?) shutdown behaviour * in the JNLP class loader (fails with * IllegalStateException: zip file closed). * Report and fix provided by Laurent Bourges. */ if ( tidierCallback != null ) { SwingUtilities.invokeLater( tidierCallback ); }; } }; return serviceGui; } }; } catch ( Throwable e ) { return new BrokenHubMode( name, e ); } } /** * Constructs a mode for MessageTrackerHubService. * * @return mode with message tracking */ private static HubServiceMode createMessageTrackerHubMode( String name ) { try { MessageTrackerHubService.class.getName(); return new HubServiceMode( name, false ) { ServiceGui createHubService( Random random, final HubProfile[] profiles, final Hub[] runners ) { final ServiceGui serviceGui = new ServiceGui(); serviceGui.service_ = new MessageTrackerHubService( random ) { Tidier tidier; public void start() { super.start(); final MessageTrackerHubService service = this; SwingUtilities.invokeLater( new Runnable() { public void run() { JFrame window = createHubWindow(); tidier = configureHubWindow( window, profiles, runners, service ); serviceGui.window_ = window; } } ); } public void shutdown() { super.shutdown(); SwingUtilities.invokeLater( new Runnable() { public void run() { if ( tidier != null ) { tidier.tidyGui(); } } } ); } }; return serviceGui; } }; } catch ( Throwable e ) { return new BrokenHubMode( name, e ); } } /** * Constructs a mode for FacadeHubService. * * @return mode based on the default client profile */ private static HubServiceMode createFacadeHubMode( String name ) { return new HubServiceMode( name, true ) { ServiceGui createHubService( Random random, HubProfile[] profiles, final Hub[] runners ) { ServiceGui serviceGui = new ServiceGui(); serviceGui.service_ = new FacadeHubService( DefaultClientProfile.getProfile() ); return serviceGui; } }; } /** * HubServiceMode implementation for modes which cannot be used because they * rely on classes unavailable at runtime. */ private static class BrokenHubMode extends HubServiceMode { private final Throwable error_; /** * Constructor. * * @param name mode name * @param error error explaining why mode is unavailable for use */ BrokenHubMode( String name, Throwable error ) { super( name, false ); error_ = error; } ServiceGui createHubService( Random random, HubProfile[] profiles, Hub[] runners ) { throw new RuntimeException( "Hub mode " + getName() + " unavailable", error_ ); } } /** * Utility abstract class to define an object which can be tidied up * on hub shutdown. */ private interface Tidier { /** * Performs any required tidying operations. * May be assumed to be called on the AWT Event Dispatch Thread. */ void tidyGui(); } /** * Aggregates a HubService and an associated monitor/control window. */ static class ServiceGui { private volatile HubService service_; private JFrame window_; /** * Returns the hub service. * * @return hub service object */ public HubService getHubService() { return service_; } /** * Returns a monitor/control window for this service, if available. * * @return window, or null */ public JFrame getWindow() { return window_; } } /** * Class to configure a window for use as a hub control. */ private static class BasicWindowConfig implements Tidier { final JFrame frame_; final Hub[] runners_; final GuiHubService hubService_; final ProfileToggler[] profileTogglers_; final ConfigHubProfile[] configProfiles_; final Action exitAct_; /** * Constructor. * * @param frame hub window * @param profiles hub profiles to run * @param runners 1-element array which will contain an associated * hub runner object if one exists * @param hubService object providing hub services */ BasicWindowConfig( JFrame frame, HubProfile[] profiles, final Hub[] runners, GuiHubService hubService ) { frame_ = frame; runners_ = runners; hubService_ = hubService; profileTogglers_ = new ProfileToggler[ profiles.length ]; List configProfileList = new ArrayList(); for ( int ip = 0; ip < profiles.length; ip++ ) { HubProfile profile = profiles[ ip ]; profileTogglers_[ ip ] = new ProfileToggler( profile, runners ); if ( profile instanceof ConfigHubProfile ) { configProfileList.add( (ConfigHubProfile) profile ); } } configProfiles_ = (ConfigHubProfile[]) configProfileList.toArray( new ConfigHubProfile[ 0 ] ); exitAct_ = new AbstractAction( "Stop Hub" ) { public void actionPerformed( ActionEvent evt ) { if ( runners[ 0 ] != null ) { runners[ 0 ].shutdown(); } tidyGui(); } }; exitAct_.putValue( Action.SHORT_DESCRIPTION, "Shut down SAMP hub" ); exitAct_.putValue( Action.MNEMONIC_KEY, new Integer( KeyEvent.VK_T ) ); } /** * Perform configuration of window. */ public void configureWindow() { configureMenus(); frame_.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); frame_.setVisible( true ); frame_.addWindowListener( new WindowAdapter() { public void windowClosed( WindowEvent evt ) { Hub runner = runners_[ 0 ]; if ( runner != null ) { runner.shutdown(); } } } ); } /** * Configures menus on the window. Invoked by configureWindow. */ protected void configureMenus() { JMenuBar mbar = new JMenuBar(); JMenu fileMenu = new JMenu( "File" ); fileMenu.setMnemonic( KeyEvent.VK_F ); fileMenu.add( new JMenuItem( exitAct_ ) ); mbar.add( fileMenu ); JMenu[] serviceMenus = hubService_.createMenus(); for ( int im = 0; im < serviceMenus.length; im++ ) { mbar.add( serviceMenus[ im ] ); } JMenu profileMenu = new JMenu( "Profiles" ); profileMenu.setMnemonic( KeyEvent.VK_P ); for ( int ip = 0; ip < profileTogglers_.length; ip++ ) { profileMenu.add( profileTogglers_[ ip ].createJMenuItem() ); } // Add configuration menus - somewhat hacky, only really intended // for Web Profile at present. for ( int ic = 0; ic < configProfiles_.length; ic++ ) { ConfigHubProfile configProfile = configProfiles_[ ic ]; JToggleButton.ToggleButtonModel[] configModels = configProfile.getConfigModels(); JMenu configMenu = new JMenu( configProfile.getProfileName() + " Profile Configuration" ); for ( int im = 0; im < configModels.length; im++ ) { JToggleButton.ToggleButtonModel model = configModels[ im ]; JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem( model.toString() ); menuItem.setModel( model ); configMenu.add( menuItem ); } profileMenu.add( configMenu ); } mbar.add( profileMenu ); frame_.setJMenuBar( mbar ); } public void tidyGui() { if ( frame_.isDisplayable() ) { frame_.dispose(); } } } /** * Takes care of hub display window configuration with system tray * functionality. */ private static class SysTrayWindowConfig extends BasicWindowConfig { private final SysTray sysTray_; private final Action showAct_; private final Action hideAct_; private final MenuItem showItem_; private final MenuItem hideItem_; private final MenuItem exitItem_; private final ActionListener iconListener_; private Object trayIcon_; /** * Constructor. * * @param frame hub window * @param profiles hub profiles to run * @param runners 1-element array which will contain an associated * hub runner object if one exists * @param hubService object providing hub services * @param sysTray system tray facade object */ SysTrayWindowConfig( JFrame frame, HubProfile[] profiles, Hub[] runners, GuiHubService hubService, SysTray sysTray ) { super( frame, profiles, runners, hubService ); sysTray_ = sysTray; showAct_ = new AbstractAction( "Show Hub Window" ) { public void actionPerformed( ActionEvent evt ) { setWindowVisible( true ); } }; hideAct_ = new AbstractAction( "Hide Hub Window" ) { public void actionPerformed( ActionEvent evt ) { setWindowVisible( false ); } }; showItem_ = toMenuItem( showAct_ ); hideItem_ = toMenuItem( hideAct_ ); exitItem_ = toMenuItem( exitAct_ ); iconListener_ = showAct_; } protected void configureMenus() { super.configureMenus(); frame_.getJMenuBar().getMenu( 0 ).add( new JMenuItem( hideAct_ ) ); } public void configureWindow() { configureMenus(); frame_.setDefaultCloseOperation( JFrame.HIDE_ON_CLOSE ); // Arrange that a manual window close will set the action states // correctly. frame_.addWindowListener( new WindowAdapter() { public void windowClosing( WindowEvent evt ) { showAct_.setEnabled( true ); hideAct_.setEnabled( false ); } } ); hideAct_.actionPerformed( null ); } /** * Performs configuration. */ public void configureSysTray() throws AWTException { Image im = Toolkit.getDefaultToolkit() .createImage( Client.class .getResource( "images/hub.png" ) ); String tooltip = "SAMP Hub"; PopupMenu popup = new PopupMenu(); Menu profileMenu = new Menu( "Profiles" ); for ( int ip = 0; ip < profileTogglers_.length; ip++ ) { profileMenu.add( profileTogglers_[ ip ].createMenuItem() ); } popup.add( profileMenu ); popup.add( showItem_ ); popup.add( hideItem_ ); popup.add( exitItem_ ); trayIcon_ = sysTray_.addIcon( im, tooltip, popup, iconListener_ ); } public void tidyGui() { super.tidyGui(); try { sysTray_.removeIcon( trayIcon_ ); } catch ( AWTException e ) { logger_.warning( "Can't remove hub system tray icon: " + e ); } } /** * Sets visibility for the hub control window, adjusting actions * as appropriate. * * @param isVis true for visible, false for invisible */ private void setWindowVisible( boolean isVis ) { frame_.setVisible( isVis ); showAct_.setEnabled( ! isVis ); hideAct_.setEnabled( isVis ); showItem_.setEnabled( ! isVis ); hideItem_.setEnabled( isVis ); } /** * Turns an action into an AWT menu item. * * @param act action * @return MenuItem facade */ private MenuItem toMenuItem( Action act ) { MenuItem item = new MenuItem( (String) act.getValue( Action.NAME ) ); item.addActionListener( act ); return item; } } /** * Manages a toggle button for starting/stopping profiles. * This object can supply both Swing JMenuItems and AWT MenuItems * with effectively the same model (which is quite hard work). */ private static class ProfileToggler { final HubProfile profile_; final Hub[] runners_; final String title_; final JToggleButton.ToggleButtonModel toggleModel_; final List menuItemList_; /** * Constructor. * * @param profile profile to operate on * @param runners one-element array containing hub */ ProfileToggler( HubProfile profile, Hub[] runners ) { profile_ = profile; runners_ = runners; title_ = profile.getProfileName() + " Profile"; menuItemList_ = new ArrayList(); toggleModel_ = new JToggleButton.ToggleButtonModel() { public boolean isSelected() { return profile_.isRunning(); } public void setSelected( boolean on ) { Hub hub = runners_[ 0 ]; if ( hub != null ) { if ( on && ! profile_.isRunning() ) { try { hub.startProfile( profile_ ); super.setSelected( on ); } catch ( IOException e ) { ErrorDialog .showError( null, title_ + " Start Error", "Error starting " + title_, e ); return; } } else if ( ! on && profile_.isRunning() ) { hub.stopProfile( profile_ ); } } super.setSelected( on ); } }; toggleModel_.addChangeListener( new ChangeListener() { public void stateChanged( ChangeEvent evt ) { updateMenuItems(); } } ); } /** * Returns a new Swing JMenuItem for start/stop toggle. * * @return menu item */ public JMenuItem createJMenuItem() { JCheckBoxMenuItem item = new JCheckBoxMenuItem( title_ ); item.setToolTipText( "Start or stop the " + title_ ); char chr = Character .toUpperCase( profile_.getProfileName().charAt( 0 ) ); if ( chr >= 'A' && chr <= 'Z' ) { item.setMnemonic( (int) chr ); } item.setModel( toggleModel_ ); return item; } /** * Returns a new AWT MenuItem for start/stop toggle. * * @return menu item */ public MenuItem createMenuItem() { final CheckboxMenuItem item = new CheckboxMenuItem( title_ ); item.addItemListener( new ItemListener() { public void itemStateChanged( ItemEvent evt ) { boolean on = item.getState(); toggleModel_.setSelected( on ); if ( toggleModel_.isSelected() != on ) { item.setState( toggleModel_.isSelected() ); } } } ); item.setState( toggleModel_.isSelected() ); menuItemList_.add( item ); return item; } /** * Updates all dispatched menu items to the current state. */ private void updateMenuItems() { for ( Iterator it = menuItemList_.iterator(); it.hasNext(); ) { CheckboxMenuItem item = (CheckboxMenuItem) it.next(); boolean on = toggleModel_.isSelected(); if ( item.getState() != on ) { item.setState( on ); } } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/KeyGenerator.java000066400000000000000000000035051356450004300252240ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.security.SecureRandom; import java.util.Random; /** * Object which can generate a sequence of private keys. * The values returned by the next() method should in general not be * easy to guess. * * @author Mark Taylor * @since 26 Oct 2010 */ public class KeyGenerator { private final String prefix_; private final int nchar_; private final Random random_; private int iseq_; private static final char SEQ_DELIM = '_'; /** * Constructor. * * @param prefix prefix prepended to all generated keys * @param nchar number of characters in generated keys * @param random random number generator */ public KeyGenerator( String prefix, int nchar, Random random ) { prefix_ = prefix; nchar_ = nchar; random_ = random; } /** * Returns the next key in the sequence. * Guaranteed different from any previous return value from this method. * * @return key string */ public synchronized String next() { StringBuffer sbuf = new StringBuffer(); sbuf.append( prefix_ ); sbuf.append( Integer.toString( ++iseq_ ) ); sbuf.append( SEQ_DELIM ); for ( int i = 0; i < nchar_; i++ ) { char c = (char) ( 'a' + (char) random_.nextInt( 'z' - 'a' ) ); assert c != SEQ_DELIM; sbuf.append( c ); } return sbuf.toString(); } /** * Returns a new, randomly seeded, Random object. * * @return random */ public static Random createRandom() { byte[] seedBytes = new SecureRandom().generateSeed( 8 ); long seed = 0L; for ( int i = 0; i < 8; i++ ) { seed = ( seed << 8 ) | ( seedBytes[ i ] & 0xff ); } return new Random( seed ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/LockWriter.java000066400000000000000000000077171356450004300247230ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.astrogrid.samp.Platform; /** * Writes records to a SAMP Standard Profile hub lockfile. * * @author Mark Taylor * @since 15 Jul 2008 */ public class LockWriter { private final OutputStream out_; private static final byte[] linesep_ = getLineSeparator(); private static final String TOKEN_REGEX = "[a-zA-Z0-9\\-_\\.]+"; private static final Pattern TOKEN_PATTERN = Pattern.compile( TOKEN_REGEX ); /** * Constructs a writer for writing to a given output stream. * * @param out output stream */ public LockWriter( OutputStream out ) { out_ = out; } /** * Writes all the assignments in a given map to the lockfile. * * @param map assignment set to output */ public void writeAssignments( Map map ) throws IOException { for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); writeAssignment( (String) entry.getKey(), (String) entry.getValue() ); } } /** * Writes a single assignment to the lockfile. * * @param name assignment key * @param value assignment value */ public void writeAssignment( String name, String value ) throws IOException { if ( TOKEN_PATTERN.matcher( name ).matches() ) { writeLine( name + "=" + value ); } else { throw new IllegalArgumentException( "Bad name sequence: " + name + " !~" + TOKEN_REGEX ); } } /** * Writes a comment line to the lockfile. * * @param comment comment text */ public void writeComment( String comment ) throws IOException { writeLine( "# " + comment ); } /** * Writes a blank line to the lockfile. */ public void writeLine() throws IOException { out_.write( linesep_ ); } /** * Writes a line of text to the lockfile, terminated with a line-end. * * @param line line to write */ protected void writeLine( String line ) throws IOException { byte[] bbuf = new byte[ line.length() ]; for ( int i = 0; i < line.length(); i++ ) { int c = line.charAt( i ); if ( c < 0x20 || c > 0x7f ) { throw new IllegalArgumentException( "Illegal character 0x" + Integer.toHexString( c ) ); } bbuf[ i ] = (byte) c; } out_.write( bbuf ); writeLine(); } /** * Closes the output stream. * May be required to ensure that all data is written. */ public void close() throws IOException { out_.close(); } /** * Sets the permissions on a given file suitably for a SAMP Standard * Profile lockfile. This means that nobody apart from the file's * owner can read it. * * @param file file to set access permissions on */ public static void setLockPermissions( File file ) throws IOException { Platform.getPlatform().setPrivateRead( file ); } /** * Returns the platform-specific line separator sequence as an array of * bytes. * * @return line separator sequence */ private static final byte[] getLineSeparator() { String linesep = System.getProperty( "line.separator" ); if ( linesep.matches( "[\\r\\n]+" ) ) { byte[] lsbuf = new byte[ linesep.length() ]; for ( int i = 0; i < linesep.length(); i++ ) { lsbuf[ i ] = (byte) linesep.charAt( i ); } return lsbuf; } else { return new byte[] { (byte) '\n' }; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/MessageRestriction.java000066400000000000000000000015621356450004300264400ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.util.Map; /** * Specifies restrictions on the message types that may be sent in * a particular context. * In general if null is used in place of a MessageRestriction object, * the understanding is that no restrictions apply. * * @author Mark Taylor * @since 23 Nov 2011 */ public interface MessageRestriction { /** * Indicates whether a message covered by a given MType subscription * may be sent. * * @param mtype the MType string to be sent * @param subsInfo the annotation map corresponding to the MType * subscription (the value from the Subscriptions map * corresponding to the mtype key) * @return true if the message may be sent, false if it is blocked */ boolean permitSend( String mtype, Map subsInfo ); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/MetaQueryMessageHandler.java000066400000000000000000000030561356450004300273450ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.client.AbstractMessageHandler; import org.astrogrid.samp.client.HubConnection; /** * Implements MType for querying registered clients by metadata item. * * @author Mark Taylor * @since 21 Nov 2011 */ class MetaQueryMessageHandler extends AbstractMessageHandler { private final ClientSet clientSet_; private static final String BASE_MTYPE = "query.by-meta"; /** * Constructor. * * @param clientSet hub client set object */ public MetaQueryMessageHandler( ClientSet clientSet ) { super( new String[] { "samp." + BASE_MTYPE, "x-samp." + BASE_MTYPE } ); clientSet_ = clientSet; } public Map processCall( HubConnection conn, String senderId, Message msg ) { String key = (String) msg.getRequiredParam( "key" ); String value = (String) msg.getRequiredParam( "value" ); HubClient[] clients = clientSet_.getClients(); List foundList = new ArrayList(); for ( int ic = 0; ic < clients.length; ic++ ) { HubClient client = clients[ ic ]; Metadata meta = client.getMetadata(); if ( meta != null && value.equals( meta.get( key ) ) ) { foundList.add( client.getId() ); } } Map result = new HashMap(); result.put( "ids", foundList ); return result; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/PingMessageHandler.java000066400000000000000000000011401356450004300263160ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.util.HashMap; import java.util.Map; import org.astrogrid.samp.Message; import org.astrogrid.samp.client.AbstractMessageHandler; import org.astrogrid.samp.client.HubConnection; /** * Implements samp.app.ping MType. * * @author Mark Taylor * @since 21 Nov 2011 */ class PingMessageHandler extends AbstractMessageHandler { /** * Constructor. */ public PingMessageHandler() { super( "samp.app.ping" ); } public Map processCall( HubConnection conn, String senderId, Message msg ) { return new HashMap(); } }; jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/ProfileToken.java000066400000000000000000000013661356450004300252310ustar00rootroot00000000000000package org.astrogrid.samp.hub; /** * Marker interface that identifies a hub profile. * Objects implementing this interface can be identified as the provider of * a connection to the hub. * * @author Mark Taylor * @since 20 Jul 2011 */ public interface ProfileToken { /** * Returns the name by which this token is to be identified. * * @return profile identifier, usually one word */ String getProfileName(); /** * Returns a MessageRestriction object which controls what messages * may be sent by clients registering under ths profile. * If null is returned, any messages may be sent. * * @return message restriction, or null */ MessageRestriction getMessageRestriction(); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/WrapperHubConnection.java000066400000000000000000000053021356450004300267210ustar00rootroot00000000000000package org.astrogrid.samp.hub; import java.util.List; import java.util.Map; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * HubConnection implementation that delegates all calls to a base instance. * * @author Mark Taylor * @since 3 Feb 2011 */ class WrapperHubConnection implements HubConnection { private final HubConnection base_; /** * Constructor. * * @param base hub connection to which all calls are delegated */ public WrapperHubConnection( HubConnection base ) { base_ = base; } public RegInfo getRegInfo() { return base_.getRegInfo(); } public void setCallable( CallableClient client ) throws SampException { base_.setCallable( client ); } public void ping() throws SampException { base_.ping(); } public void unregister() throws SampException { base_.unregister(); } public void declareMetadata( Map meta ) throws SampException { base_.declareMetadata( meta ); } public Metadata getMetadata( String clientId ) throws SampException { return base_.getMetadata( clientId ); } public void declareSubscriptions( Map subs ) throws SampException { base_.declareSubscriptions( subs ); } public Subscriptions getSubscriptions( String clientId ) throws SampException { return base_.getSubscriptions( clientId ); } public String[] getRegisteredClients() throws SampException { return base_.getRegisteredClients(); } public Map getSubscribedClients( String mtype ) throws SampException { return base_.getSubscribedClients( mtype ); } public void notify( String recipientId, Map msg ) throws SampException { base_.notify( recipientId, msg ); } public List notifyAll( Map msg ) throws SampException { return base_.notifyAll( msg ); } public String call( String recipientId, String msgTag, Map msg ) throws SampException { return base_.call( recipientId, msgTag, msg ); } public Map callAll( String msgTag, Map msg ) throws SampException { return base_.callAll( msgTag, msg ); } public Response callAndWait( String recipientId, Map msg, int timeout ) throws SampException { return base_.callAndWait( recipientId, msg, timeout ); } public void reply( String msgId, Map response ) throws SampException { base_.reply( msgId, response ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/hub/package.html000066400000000000000000000001711356450004300242370ustar00rootroot00000000000000 Classes required only for running a SAMP hub. For standard usage, see {@link org.astrogrid.samp.hub.Hub}. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/package.html000066400000000000000000000000731356450004300234620ustar00rootroot00000000000000 Classes common to SAMP hub and client code. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/000077500000000000000000000000001356450004300221605ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/CalcStorm.java000066400000000000000000000253751356450004300247260ustar00rootroot00000000000000package org.astrogrid.samp.test; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFrame; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.gui.HubMonitor; /** * Runs a load of Calculator clients at once all sending messages to each other. * Suitable for load testing or benchmarking a hub. * * @author Mark Taylor * @since 22 Jul 2008 */ public class CalcStorm { private final ClientProfile profile_; private final Random random_; private final int nClient_; private final int nQuery_; private final Calculator.SendMode sendMode_; private static final Logger logger_ = Logger.getLogger( CalcStorm.class.getName() ); /** * Constructor. * * @param profile hub connection factory * @param random random number generator * @param nClient number of clients to run * @param nQuery number of messages each client will send * @param sendMode delivery pattern for messages */ public CalcStorm( ClientProfile profile, Random random, int nClient, int nQuery, Calculator.SendMode sendMode ) { profile_ = profile; random_ = random; nClient_ = nClient; nQuery_ = nQuery; sendMode_ = sendMode; } /** * Runs a lot of calculators at once all talking to each other. * * @throws TestException if any tests fail */ public void run() throws IOException { // Set up clients. final Calculator[] calcs = new Calculator[ nClient_ ]; final String[] ids = new String[ nClient_ ]; final Random[] randoms = new Random[ nClient_ ]; for ( int ic = 0; ic < nClient_; ic++ ) { HubConnection conn = profile_.register(); if ( conn == null ) { throw new IOException( "No hub is running" ); } randoms[ ic ] = new Random( random_.nextLong() ); ids[ ic ] = conn.getRegInfo().getSelfId(); calcs[ ic ] = new Calculator( conn, randoms[ ic ] ); } // Set up one thread per client to do the message sending. Thread[] calcThreads = new Thread[ nClient_ ]; final Throwable[] errors = new Throwable[ 1 ]; for ( int ic = 0; ic < nClient_; ic++ ) { final Calculator calc = calcs[ ic ]; final Random random = randoms[ ic ]; calcThreads[ ic ] = new Thread( "Calc" + ic ) { public void run() { try { for ( int iq = 0; iq < nQuery_ && errors[ 0 ] == null; iq++ ) { calc.sendMessage( ids[ random.nextInt( nClient_ ) ], sendMode_ ); } calc.flush(); } catch ( Throwable e ) { errors[ 0 ] = e; } } }; } // Start the threads running. for ( int ic = 0; ic < nClient_; ic++ ) { calcThreads[ ic ].start(); } // Wait for all the threads to finish. try { for ( int ic = 0; ic < nClient_; ic++ ) { calcThreads[ ic ].join(); } } catch ( InterruptedException e ) { throw new TestException( "Interrupted", e ); } // If we are using the notification delivery pattern, wait until // all the clients have received all the messages they are expecting. // In the case of call/response this is not necessary, since the // message sender threads will only complete their run() methods // when the responses have come back, which must mean that the // messages arrived at their recipients. if ( sendMode_ == Calculator.NOTIFY_MODE || sendMode_ == Calculator.RANDOM_MODE ) { for ( boolean done = false; ! done; ) { int totCalc = 0; for ( int ic = 0; ic < nClient_; ic++ ) { totCalc += calcs[ ic ].getReceiveCount(); } done = totCalc >= nClient_ * nQuery_; if ( ! done ) { Thread.yield(); } } } // Unregister the clients. for ( int ic = 0; ic < nClient_; ic++ ) { calcs[ ic ].getConnection().unregister(); } // If any errors occurred on the sending thread, rethrow one of them // here. if ( errors[ 0 ] != null ) { throw new TestException( "Error in calculator thread: " + errors[ 0 ].getMessage(), errors[ 0 ] ); } // Check that the number of messages sent and the number received // was what it should have been. int totCalc = 0; for ( int ic = 0; ic < nClient_; ic++ ) { Calculator calc = calcs[ ic ]; Tester.assertEquals( nQuery_, calc.getSendCount() ); totCalc += calc.getReceiveCount(); } Tester.assertEquals( totCalc, nClient_ * nQuery_ ); } /** * Does the work for the main method. * Use -help flag for documentation. * * @param args command-line arguments * @return 0 means success */ public static int runMain( String[] args ) throws IOException { // Set up usage message. String usage = new StringBuffer() .append( "\n Usage:" ) .append( "\n " ) .append( CalcStorm.class.getName() ) .append( "\n " ) .append( " [-help]" ) .append( " [-/+verbose]" ) .append( "\n " ) .append( " [-gui]" ) .append( " [-nclient ]" ) .append( " [-nquery ]" ) .append( "\n " ) .append( " [-mode sync|async|notify|random]" ) .append( "\n" ) .toString(); // Prepare default values for test. Random random = new Random( 2333333 ); int nClient = 20; int nQuery = 100; Calculator.SendMode sendMode = Calculator.RANDOM_MODE; int verbAdjust = 0; boolean gui = false; // Parse arguments, modifying test parameters as appropriate. List argList = new ArrayList( Arrays.asList( args ) ); try { for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.startsWith( "-nc" ) && it.hasNext() ) { it.remove(); String snc = (String) it.next(); it.remove(); nClient = Integer.parseInt( snc ); } else if ( arg.startsWith( "-nq" ) && it.hasNext() ) { it.remove(); String snq = (String) it.next(); it.remove(); nQuery = Integer.parseInt( snq ); } else if ( arg.equals( "-mode" ) && it.hasNext() ) { it.remove(); String smode = (String) it.next(); it.remove(); final Calculator.SendMode sm; if ( smode.toLowerCase().startsWith( "sync" ) ) { sm = Calculator.SYNCH_MODE; } else if ( smode.toLowerCase().startsWith( "async" ) ) { sm = Calculator.ASYNCH_MODE; } else if ( smode.toLowerCase().startsWith( "notif" ) ) { sm = Calculator.NOTIFY_MODE; } else if ( smode.toLowerCase().startsWith( "rand" ) ) { sm = Calculator.RANDOM_MODE; } else { System.err.println( usage ); return 1; } sendMode = sm; } else if ( arg.equals( "-gui" ) ) { it.remove(); gui = true; } else if ( arg.equals( "-nogui" ) ) { it.remove(); gui = false; } else if ( arg.startsWith( "-v" ) ) { it.remove(); verbAdjust--; } else if ( arg.startsWith( "+v" ) ) { it.remove(); verbAdjust++; } else if ( arg.startsWith( "-h" ) ) { System.out.println( usage ); return 0; } else { System.err.println( usage ); return 1; } } } catch ( RuntimeException e ) { System.err.println( usage ); return 1; } if ( ! argList.isEmpty() ) { System.err.println( usage ); return 1; } // Adjust logging in accordance with verboseness flags. int logLevel = Level.WARNING.intValue() + 100 * verbAdjust; Logger.getLogger( "org.astrogrid.samp" ) .setLevel( Level.parse( Integer.toString( logLevel ) ) ); // Prepare profile. ClientProfile profile = DefaultClientProfile.getProfile(); // Set up GUI monitor if required. JFrame frame; if ( gui ) { frame = new JFrame( "CalcStorm Monitor" ); frame.getContentPane().add( new HubMonitor( profile, true, 1 ) ); frame.pack(); frame.setVisible( true ); } else { frame = null; } // Run the test. long start = System.currentTimeMillis(); new CalcStorm( profile, random, nClient, nQuery, sendMode ).run(); long time = System.currentTimeMillis() - start; System.out.println( "Elapsed time: " + time + " ms" + " (" + (int) ( time * 1000. / ( nClient * nQuery ) ) + " us per message)" ); // Tidy up and return. if ( frame != null ) { frame.dispose(); } return 0; } /** * Main method. Use -help flag. */ public static void main( String[] args ) throws IOException { int status = runMain( args ); if ( status != 0 ) { System.exit( status ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/Calculator.java000066400000000000000000000303251356450004300251170ustar00rootroot00000000000000package org.astrogrid.samp.test; import java.io.IOException; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.logging.Logger; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.UtilServer; /** * Test client. Performs simple integer arithmetic. * * @author Mark Taylor * @since 21 Jul 2008 */ public class Calculator extends Tester implements CallableClient { private final HubConnection connection_; private final Map callMap_; private final Random random_; private volatile int nCalc_; private volatile int nCall_; private static final String ADD_MTYPE = "calc.int.add"; private static final String SUB_MTYPE = "calc.int.sub"; private static final String MUL_MTYPE = "calc.int.mul"; private static final String DIV_MTYPE = "calc.int.div"; private static final Logger logger_ = Logger.getLogger( Calculator.class.getName() ); private static String iconUrl_; /** Sends messages using the Notify delivery pattern. */ public static final SendMode NOTIFY_MODE = new SendMode( "notify" ) { void send( Calculator calc, String receiverId, CalcRequest request, int iseq ) throws SampException { calc.connection_.notify( receiverId, request.getMessage() ); } }; /** Sends messages using the Synchronous Call/Response delivery pattern. */ public static final SendMode ASYNCH_MODE = new SendMode( "sync" ) { void send( Calculator calc, String receiverId, CalcRequest request, int iseq ) throws SampException { String msgTag = "tag-" + iseq; synchronized ( calc.callMap_ ) { calc.callMap_.put( msgTag, request ); } calc.connection_.call( receiverId, msgTag, request.getMessage() ); } }; /** Sends messages using the Asynchronous Call/Response delivery pattern. */ public static final SendMode SYNCH_MODE = new SendMode( "async" ) { void send( Calculator calc, String receiverId, CalcRequest request, int iseq ) throws SampException { Response response = calc.connection_.callAndWait( receiverId, request.getMessage(), 0 ); request.checkResponse( response ); } }; /** Sends messages using a random choice of one of the other modes. */ public static final SendMode RANDOM_MODE = new SendMode( "mixture" ) { private final SendMode[] otherModes = new SendMode[] { NOTIFY_MODE, ASYNCH_MODE, SYNCH_MODE, }; void send( Calculator calc, String receiverId, CalcRequest request, int iseq ) throws SampException { otherModes[ calc.random_.nextInt( otherModes.length ) ] .send( calc, receiverId, request, iseq ); } }; /** * Constructor. * * @param connection hub connection * @param random random number generator */ public Calculator( HubConnection connection, Random random ) throws SampException { connection_ = connection; random_ = random; callMap_ = Collections.synchronizedMap( new HashMap() ); connection_.setCallable( this ); Metadata meta = new Metadata(); meta.setName( "Calculator" ); meta.setDescriptionText( "Rudimentary integer arithmetic application" ); String iconUrl = getIconUrl(); if ( iconUrl != null ) { meta.setIconUrl( iconUrl ); } connection_.declareMetadata( meta ); Subscriptions subs = new Subscriptions(); subs.addMType( ADD_MTYPE ); subs.addMType( SUB_MTYPE ); subs.addMType( MUL_MTYPE ); subs.addMType( DIV_MTYPE ); connection_.declareSubscriptions( subs ); } /** * Sends a randomly generated message in a randomly generated way to * a given receiver. The receiver should be another calculator client, * like this one. If the message is sent according to one of the * call/response delivery patterns the response will be checked to * ensure that it has the correct value. * * @param receiverId client ID of another Calculator client. */ public void sendMessage( String receiverId, SendMode mode ) throws SampException { mode.send( this, receiverId, createRandomRequest(), nextCall() ); } /** * Returns the total number of messages sent using any delivery pattern. * * @return number of sends */ public int getSendCount() { return nCall_; } /** * Returns the total number of messages received using any delivery pattern. * * @return number of receives */ public int getReceiveCount() { return nCalc_; } /** * Returns the hub connection used by this client. * * @return connection */ public HubConnection getConnection() { return connection_; } /** * Waits until all the responses this client is expecting to get * have been safely received. */ public void flush() { try { synchronized ( callMap_ ) { while ( ! callMap_.isEmpty() ) { callMap_.wait( 100 ); } } } catch ( InterruptedException e ) { throw new RuntimeException( "Interrupted" ); } } public void receiveNotification( String senderId, Message msg ) { processCall( senderId, msg ); } public void receiveCall( String senderId, String msgId, Message msg ) throws SampException { Response response; try { response = Response .createSuccessResponse( processCall( senderId, msg ) ); } catch ( Throwable e ) { response = Response.createErrorResponse( new ErrInfo( e ) ); } connection_.reply( msgId, response ); } public void receiveResponse( String senderId, String msgTag, Response response ) { CalcRequest request; synchronized ( callMap_ ) { request = (CalcRequest) callMap_.remove( msgTag ); } request.checkResponse( response ); } /** * Does the work for both the receiveNotify and receiveCall methods. * * @param senderId sender public ID * @param msg message object * @return content of the successful reply's samp.result entry */ private Map processCall( String senderId, Message msg ) { String mtype = msg.getMType(); if ( ADD_MTYPE.equals( mtype ) || SUB_MTYPE.equals( mtype ) || MUL_MTYPE.equals( mtype ) || DIV_MTYPE.equals( mtype ) ) { synchronized ( this ) { nCalc_++; } int a = SampUtils.decodeInt( (String) msg.getParam( "a" ) ); int b = SampUtils.decodeInt( (String) msg.getParam( "b" ) ); final int x; if ( ADD_MTYPE.equals( mtype ) ) { x = a + b; } else if ( SUB_MTYPE.equals( mtype ) ) { x = a - b; } else if ( MUL_MTYPE.equals( mtype ) ) { x = a * b; } else if ( DIV_MTYPE.equals( mtype ) ) { x = a / b; } else { throw new AssertionError(); } Map result = new HashMap(); result.put( "x", SampUtils.encodeInt( x ) ); return result; } else { throw new TestException(); } } /** * Increments and then returns the number of calls so far made by this * object. * * @return next value of the call counter */ private synchronized int nextCall() { return ++nCall_; } /** * Generates a random calculation request. * * @return new random request */ private CalcRequest createRandomRequest() { String mtype = new String[] { ADD_MTYPE, SUB_MTYPE, MUL_MTYPE, DIV_MTYPE, }[ random_.nextInt( 4 ) ]; return new CalcRequest( mtype, random_.nextInt( 1000 ), 500 + random_.nextInt( 500 ) ); } private static String getIconUrl() { if ( iconUrl_ == null ) { String resource = "/org/astrogrid/samp/images/tinycalc.gif"; URL url; try { url = UtilServer.getInstance().exportResource( resource ); } catch ( IOException e ) { url = null; logger_.warning( "Can't locate icon: " + resource ); } iconUrl_ = url == null ? "" : url.toString(); } return iconUrl_.length() > 0 ? iconUrl_ : null; } /** * Represents a delivery pattern. * Instances are provided as static members of class {@link Calculator}. */ public static abstract class SendMode { private final String name_; /** * Constructor. * * @param name mode name */ private SendMode( String name ) { name_ = name; } /** * Sends a message from one calculator client to another using this * send mode. * * @param calc sending client * @param receiverId public ID of receiving client * @param request calculation request object * @param iseq unique identifier for this request by this calculator */ abstract void send( Calculator calc, String receiverId, CalcRequest request, int iseq ) throws SampException; /** * Returns the name of this mode. */ public String toString() { return name_; } } /** * Represents a request which may be sent to a Calculator object. */ private class CalcRequest { private final int a_; private final int b_; private final String mtype_; private final int x_; /** * Constructor. * * @param mtype operation type as an MType string * @param a first parameter * @param b second parameter */ public CalcRequest( String mtype, int a, int b ) { mtype_ = mtype; a_ = a; b_ = b; if ( ADD_MTYPE.equals( mtype ) ) { x_ = a + b; } else if ( SUB_MTYPE.equals( mtype ) ) { x_ = a - b; } else if ( MUL_MTYPE.equals( mtype ) ) { x_ = a * b; } else if ( DIV_MTYPE.equals( mtype ) ) { x_ = a / b; } else { throw new IllegalArgumentException(); } } /** * Returns a Message object corresponding to this request. */ public Message getMessage() { Message msg = new Message( mtype_ ); msg.addParam( "a", SampUtils.encodeInt( a_ ) ); msg.addParam( "b", SampUtils.encodeInt( b_ ) ); return msg; } /** * Checks that the given response is correct for this request. * * @param response response to check */ public void checkResponse( Response response ) { assertEquals( Response.OK_STATUS, response.getStatus() ); assertEquals( x_, SampUtils.decodeInt( (String) response.getResult() .get( "x" ) ) ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/HubTester.java000066400000000000000000001330211356450004300247300ustar00rootroot00000000000000package org.astrogrid.samp.test; import java.io.IOException; import java.net.URL; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFrame; import org.astrogrid.samp.Client; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.gui.HubMonitor; import org.astrogrid.samp.xmlrpc.LockInfo; import org.astrogrid.samp.xmlrpc.StandardClientProfile; /** * Tester for a running hub. * Attempts to test as much of the SAMP standard as possible for an existing * hub implementation. * * @author Mark Taylor * @since 18 Jul 2008 */ public class HubTester extends Tester { private final ClientProfile profile_; private final String hubId_; private final Client[] ignoreClients_; private final Set selfIds_; private final Set privateKeys_; private final ClientWatcher clientWatcher_; private final Random random_ = new Random( 233333L ); private static final String WAITMILLIS_KEY = "test.wait"; private static final String MSGIDQUERY_KEY = "test.msgid"; private static final String ECHO_MTYPE = "test.echo"; private static final String PING_MTYPE = "samp.app.ping"; private static final String FAIL_MTYPE = "test.fail"; private static final String REGISTER_MTYPE = "samp.hub.event.register"; private static final String UNREGISTER_MTYPE = "samp.hub.event.unregister"; private static final String METADATA_MTYPE = "samp.hub.event.metadata"; private static final String SUBSCRIPTIONS_MTYPE = "samp.hub.event.subscriptions"; private static final String ERROR_KEY = "test.error"; private static final char[] ALPHA_CHARS = createAlphaCharacters(); private static final char[] GENERAL_CHARS = createGeneralCharacters(); private static Logger logger_ = Logger.getLogger( HubTester.class.getName() ); /** * Constructor. * * @param profile hub discovery object */ public HubTester( ClientProfile profile ) throws IOException { profile_ = profile; selfIds_ = new HashSet(); privateKeys_ = new HashSet(); // Set up basic information about the hub for use by other methods. // Perform some checks at the same time. // First register a probe client to make some basic queries about the // current hub state. HubConnection conn = profile_.register(); if ( conn == null ) { throw new IOException( "No hub is running" ); } conn.ping(); // Set up monitor to receive hub event messages. clientWatcher_ = new ClientWatcher( conn ); conn.setCallable( clientWatcher_ ); conn.declareSubscriptions( clientWatcher_.getSubscriptions() ); conn.declareMetadata( clientWatcher_.getMetadata() ); // Acquire, check and store information about the hub. RegInfo regInfo = conn.getRegInfo(); regInfo.check(); hubId_ = regInfo.getHubId(); // Keep a record of all clients which have registered on behalf of // this class. selfIds_.add( regInfo.getSelfId() ); privateKeys_.add( regInfo.getPrivateKey() ); // Get list of registered clients. String[] clientIds = conn.getRegisteredClients(); // Check that the hub's ID does appear in the list. assertTrue( Arrays.asList( clientIds ).contains( regInfo.getHubId() ) ); // Check that a client's own ID does not appear in the list. assertTrue( ! Arrays.asList( clientIds ) .contains( regInfo.getSelfId() ) ); // But prepare a list which contains the client's ID as well. String[] clientIds1 = new String[ clientIds.length + 1 ]; System.arraycopy( clientIds, 0, clientIds1, 0, clientIds.length ); clientIds1[ clientIds.length ] = regInfo.getSelfId(); clientIds = clientIds1; // Check that the metadata and subscriptions of all the existing // clients is legal. int nc = clientIds.length; Client[] clients = new Client[ nc ]; for ( int ic = 0; ic < nc; ic++ ) { final String id = clientIds[ ic ]; final Metadata meta = conn.getMetadata( id ); meta.check(); final Subscriptions subs = conn.getSubscriptions( id ); subs.check(); clients[ ic ] = new Client() { public String getId() { return id; } public Metadata getMetadata() { return meta; } public Subscriptions getSubscriptions() { return subs; } }; } ignoreClients_ = clients; } /** * Registers with the hub, performing various checks. * * @return new hub connection representing a newly-registered client */ private HubConnection register() throws SampException { HubConnection conn = profile_.register(); RegInfo regInfo = conn.getRegInfo(); regInfo.check(); String selfId = regInfo.getSelfId(); String privateKey = regInfo.getPrivateKey(); // Check that the hub ID is the same for all clients. assertEquals( hubId_, regInfo.getHubId() ); // Check that client IDs are not being reused. assertTrue( ! selfIds_.contains( selfId ) ); selfIds_.add( selfId ); // Check that private keys are not being reused. assertTrue( ! privateKeys_.contains( privateKey ) ); privateKeys_.add( privateKey ); // Check that getRegisteredClients() excludes the caller's ID. assertTrue( ! Arrays.asList( conn.getRegisteredClients() ) .contains( selfId ) ); // Check that metadata and subscriptions for this application are // empty maps (since we have not yet declared their values). assertEquals( new HashMap(), conn.getMetadata( selfId ) ); assertEquals( new HashMap(), conn.getSubscriptions( selfId ) ); return conn; } /** * Perform a wide variety of tests on a running hub. */ public void run() throws IOException { // Profile-specific tests. if ( profile_ == StandardClientProfile.getInstance() ) { testStandardLockfile(); } if ( profile_ instanceof StandardClientProfile ) { testLockInfo( ((StandardClientProfile) profile_).getLockInfo() ); } // General tests. testClients(); testStress(); } /** * Tests the content of the SAMP Standard Profile lockfile. * Does not currently test the permissions on it - that would be nice * but is hard to do from java since it's rather platform-specific. */ private void testStandardLockfile() throws IOException { LockInfo lockInfo = StandardClientProfile.getInstance().getLockInfo(); if ( lockInfo == null ) { throw new TestException( "No lockfile (no hub)" ); } } /** * Does tests on a LockInfo object used by the profile. * This is specific to a Standard-like (though not necessarily Standard) * profile, and will simply be skipped for non-standard profiles. * * @param lockInfo lock info object describing a running hub */ private void testLockInfo( LockInfo lockInfo ) throws IOException { if ( lockInfo == null ) { throw new TestException( "No LockInfo (no hub)" ); } lockInfo.check(); String secret = lockInfo.getSecret(); URL hubUrl = lockInfo.getXmlrpcUrl(); TestXmlrpcClient xclient = new TestXmlrpcClient( hubUrl ); xclient.checkSuccessCall( "samp.hub.ping", new ArrayList() ); xclient.checkFailureCall( "samp.hub.register", Collections.singletonList( secret + "-NOT!" ) ); xclient.checkFailureCall( "samp.hub.not-a-method", Collections.singletonList( secret ) ); } /** * Performs a wide variety of tests on a running hub from a limited * number of clients. */ private void testClients() throws IOException { // Register client0, set metadata and subscriptions, and unregister. HubConnection c0 = register(); String id0 = c0.getRegInfo().getSelfId(); Metadata meta0 = new Metadata(); meta0.setName( "Shorty" ); meta0.setDescriptionText( "Short-lived test client" ); c0.declareMetadata( meta0 ); TestCallableClient callable0 = new TestCallableClient( c0 ); c0.setCallable( callable0 ); Subscriptions subs0 = new Subscriptions(); subs0.put( ECHO_MTYPE, new HashMap() ); subs0.check(); c0.declareSubscriptions( subs0 ); c0.unregister(); // Register client 1 with the hub and declare some metadata. HubConnection c1 = register(); String id1 = c1.getRegInfo().getSelfId(); Metadata meta1 = new Metadata(); meta1.setName( "Test1" ); meta1.setDescriptionText( "HubTester client application" ); meta1.put( "test.drink", "cider" ); c1.declareMetadata( meta1 ); // Check the list of clients known to the hub. assertTestClients( c1, new String[ 0 ] ); // Register client 2 with the hub and declare some metadata. HubConnection c2 = register(); String id2 = c2.getRegInfo().getSelfId(); Metadata meta2 = new Metadata( meta1 ); meta2.put( "test.drink", "ribena" ); c2.declareMetadata( meta2 ); // Check the list of clients known to the hub. assertTestClients( c2, new String[] { id1 } ); assertTestClients( c1, new String[] { id2 } ); // Check retrieved metadata matches declared metadata. assertEquals( meta1, c1.getMetadata( id1 ) ); assertEquals( meta1, c2.getMetadata( id1 ) ); assertEquals( meta2, c1.getMetadata( id2 ) ); assertEquals( meta2, c2.getMetadata( id2 ) ); assertEquals( "cider", c2.getMetadata( id1 ).get( "test.drink" ) ); // Redeclare metadata and check that the update has been noted. meta1.put( "test.drink", "scrumpy" ); c1.declareMetadata( meta1 ); assertEquals( meta1, c1.getMetadata( id1 ) ); assertEquals( meta1, c2.getMetadata( id1 ) ); assertEquals( "scrumpy", c2.getMetadata( id1 ).get( "test.drink" ) ); // Declare subscriptions and check that retrieved subscriptions match // declared ones. TestCallableClient callable1 = new TestCallableClient( c1 ); c1.setCallable( callable1 ); Subscriptions subs1 = new Subscriptions(); subs1.put( "test.dummy.1", new HashMap() ); subs1.put( "test.dummy.2", new HashMap() ); c1.declareSubscriptions( subs1 ); assertEquals( subs1, c1.getSubscriptions( id1 ) ); assertEquals( subs1, c2.getSubscriptions( id1 ) ); Map d3atts = new HashMap(); d3atts.put( "size", "big" ); d3atts.put( "colour", "blue" ); subs1.put( "test.dummy.3", d3atts ); c1.declareSubscriptions( subs1 ); assertEquals( subs1, c2.getSubscriptions( id1 ) ); assertEquals( new HashMap(), ((Map) c2.getSubscriptions( id1 ).get( "test.dummy.1")) ); assertEquals( "big", ((Map) c2.getSubscriptions( id1 ).get( "test.dummy.3" )) .get( "size" ) ); c1.declareSubscriptions( TestCallableClient.SUBS ); TestCallableClient callable2 = new TestCallableClient( c2 ); c2.setCallable( callable2 ); c2.declareSubscriptions( TestCallableClient.SUBS ); // Try to acquire information about non-existent clients. try { c1.getMetadata( "Sir Not-Appearing-in-this-Hub" ); fail(); } catch ( SampException e ) { } try { c1.getSubscriptions( "Sir Not-Appearing-in-this-Hub" ); fail(); } catch ( SampException e ) { } // New client which can re-use the same tag (not advisable, but legal). HubConnection c4 = register(); TestCallableClient callable4 = new TestCallableClient( c4 ); callable4.setAllowTagReuse( true ); c4.setCallable( callable4 ); // Send some concurrent ECHO messages via both notify and call. int necho = 5; Map[] echoParams = new Map[ necho ]; String[] msgIds2 = new String[ necho ]; String[] msgIds4 = new String[ necho ]; for ( int i = 0; i < necho; i++ ) { Message msg = new Message( ECHO_MTYPE ); Object val1 = createRandomObject( 2, false ); Object val2 = createRandomObject( 4, false ); Object val3 = new String( GENERAL_CHARS ); msg.put( WAITMILLIS_KEY, SampUtils.encodeInt( 200 + 100 * i ) ); msg.put( MSGIDQUERY_KEY, SampUtils.encodeBoolean( true ) ); msg.addParam( "val1", val1 ); msg.addParam( "val2", val2 ); msg.addParam( "val3", val3 ); echoParams[ i ] = msg.getParams(); c2.notify( id1, msg ); msgIds2[ i ] = callable2.call( id1, "tag" + i, msg ); msgIds4[ i ] = callable4.call( id1, "sametag", msg ); } // The call messages should complete quickly, so all the sends // are expected to complete before any of the replies are received // (there is a deliberate delay at the receiver end in all cases). // This isn't required, but hubs SHOULD work this way. // Warn if it looks like not. if ( callable2.getReplyCount() > necho / 2 ) { logger_.warning( "Looks like hub call()/notify() methods " + "not completing quickly" + " (" + callable2.getReplyCount() + "/" + necho + ")" ); } // Spin-wait until all the replies are in. while ( callable2.getReplyCount() < necho || callable4.getReplyCount() < necho ) delay( 100 ); assertEquals( necho, callable2.getReplyCount() ); assertEquals( necho, callable4.getReplyCount() ); // Check that the replies are as expected (returned samp.result has // same content as sent samp.params). for ( int i = 0; i < necho; i++ ) { assertEquals( necho - i, callable2.getReplyCount() ); Response r2 = callable2.getReply( id1, "tag" + i ); assertEquals( Response.OK_STATUS, r2.getStatus() ); assertEquals( echoParams[ i ], r2.getResult() ); assertEquals( msgIds2[ i ], r2.get( MSGIDQUERY_KEY ) ); assertEquals( necho - i, callable4.getReplyCount() ); Response r4 = callable4.getReply( id1, "sametag" ); assertEquals( Response.OK_STATUS, r4.getStatus() ); } // Check that no more replies have arrived apart from the ones we // were expecting. assertEquals( 0, callable2.getReplyCount() ); assertEquals( 0, callable4.getReplyCount() ); // c4 no longer needed. c4.unregister(); // Send echo messages synchronously (using callAndWait). // These have deliberate delays at the receiver end, but there is // no timeout. for ( int i = 0; i < necho; i++ ) { Message msg = new Message( ECHO_MTYPE ); Object val1 = createRandomObject( 2, false ); Object val2 = createRandomObject( 4, false ); Object val3 = new String( GENERAL_CHARS ); msg.put( WAITMILLIS_KEY, SampUtils.encodeInt( 100 * i ) ); msg.addParam( "val1", val1 ); msg.addParam( "val2", val2 ); msg.addParam( "val3", val3 ); echoParams[ i ] = msg.getParams(); Response syncR = c2.callAndWait( id1, msg, 0 ); assertEquals( echoParams[ i ], syncR.getResult() ); } // Send some longer messages synchronously. { for ( int ie = 0; ie < 5; ie++ ) { int num = (int) Math.pow( 10, ie ); List list = new ArrayList( num ); for ( int in = 0; in < num; in++ ) { list.add( SampUtils.encodeInt( in + 1 ) ); } Message msg = new Message( ECHO_MTYPE ); msg.addParam( "list", list ); msg.check(); Response response = c2.callAndWait( id1, msg, 0 ); response.check(); assertEquals( Response.OK_STATUS, response.getStatus() ); assertEquals( list, response.getResult().get( "list" ) ); } } // Send an echo message synchronously, with a timeout which is shorter // than the delay which the receiver will introduce. So the hub // SHOULD time this attempt out before the response is received. // However, the standard does not REQUIRE this, so just warn in // case of no time out. { Message msg = new Message( ECHO_MTYPE ); msg.addParam( "text", "copy" ); int delay = 10000; msg.put( WAITMILLIS_KEY, SampUtils.encodeInt( delay ) ); long start = System.currentTimeMillis(); try { c2.callAndWait( id1, msg, 1 ); assert System.currentTimeMillis() - start >= delay; logger_.warning( "callAndWait() did not timeout as requested" ); } catch ( SampException e ) { // timeout exception } } // Register a new client. HubConnection c3 = register(); TestCallableClient callable3 = new TestCallableClient( c3 ); c3.setCallable( callable3 ); // Test callAll and notifyAll. { // Check that getSubscribedClients returns the right list. // Note that it must not include the caller. Set recipientSet = c3.getSubscribedClients( ECHO_MTYPE ).keySet(); try { assertEquals( new HashSet( Arrays.asList( new String[] { id1, id2 } ) ), recipientSet ); } catch ( TestException e ) { throw new TestException( "You may need to shut down other " + "SAMP clients first", e ); } // Send an echo message from client 3 using callAll and notifyAll. // Should go to clients 1 and 2. Message msg = new Message( ECHO_MTYPE ); Object val4 = createRandomObject( 4, false ); msg.addParam( "val4", val4 ); msg.put( WAITMILLIS_KEY, SampUtils.encodeInt( 400 ) ); List notifyList = c3.notifyAll( msg ); assertEquals( recipientSet, new HashSet( notifyList ) ); String tag = "tag99"; msg.put( MSGIDQUERY_KEY, "1" ); Map callMap = callable3.callAll( tag, msg ); if ( callable3.getReplyCount() != 0 ) { logger_.warning( "Looks like hub call()/notify() methods " + "not completing quickly" ); } // Retrieve and check the results from the callAll for each // recipient client. for ( Iterator it = recipientSet.iterator(); it.hasNext(); ) { String rid = (String) it.next(); Response response = callable3.waitForReply( rid, tag ); assertEquals( Response.OK_STATUS, response.getStatus() ); assertEquals( val4, response.getResult().get( "val4" ) ); assertEquals( (String) callMap.get( rid ), response.get( MSGIDQUERY_KEY ) ); } // Check there are no replies beyond the ones we expect. assertEquals( 0, callable3.getReplyCount() ); delay( 500 ); // .. even after a while. assertEquals( 0, callable3.getReplyCount() ); } // Test that notify- and call-type messages are being received by // their intended recipients. { Message pingMsg = new Message( PING_MTYPE ); pingMsg.put( WAITMILLIS_KEY, SampUtils.encodeInt( 100 ) ); int pingsCount = 50; Set recipients = c3.getSubscribedClients( PING_MTYPE ).keySet(); assertTrue( recipients.contains( id1 ) ); assertTrue( recipients.contains( id2 ) ); c3.declareSubscriptions( TestCallableClient.SUBS ); assertEquals( recipients, c3.getSubscribedClients( PING_MTYPE ).keySet() ); // Send a load of messages concurrently using various // asynchronous methods. for ( int i = 0; i < pingsCount; i++ ) { c3.notify( id1, pingMsg ); callable3.call( id1, "abc1-" + i, pingMsg ); List notifyList = c3.notifyAll( pingMsg ); Map callMap = callable3.callAll( "abc2-" + i, pingMsg ); assertEquals( recipients, new HashSet( notifyList ) ); assertEquals( recipients, callMap.keySet() ); } // Spin-wait until all the clients have received all the messages // we have sent. int np1 = pingsCount * 4; int np2 = pingsCount * 2; int np3 = 0; int nr3 = pingsCount * ( 1 + recipients.size() ); while ( callable1.pingCount_ < np1 || callable2.pingCount_ < np2 || callable3.pingCount_ < np3 || callable3.getReplyCount() < nr3 ) delay( 100 ); // And then wait a bit to see if any more come in (hopefully not). delay( 400 ); // Check that the number of messages received is exactly as // expected. assertEquals( np1, callable1.pingCount_ ); assertEquals( np2, callable2.pingCount_ ); assertEquals( np3, callable3.pingCount_ ); assertEquals( nr3, callable3.getReplyCount() ); // Redeclare client 3's subscriptions to the effect that it // will not receive any messages. c3.declareSubscriptions( new Subscriptions() ); } // Test that error data encoded in responses works as it should. { Message failMsg = new Message( FAIL_MTYPE ); Map error = new HashMap(); error.put( "samp.errortxt", "failure" ); error.put( "samp.code", "999" ); error.put( "do.what", "do.that" ); failMsg.addParam( ERROR_KEY, error ); Response reply = c3.callAndWait( id2, failMsg, 0 ); ErrInfo errInfo = reply.getErrInfo(); errInfo.check(); assertEquals( Response.ERROR_STATUS, reply.getStatus() ); assertEquals( "failure", errInfo.getErrortxt() ); assertEquals( "999", errInfo.getCode() ); assertEquals( "do.that", errInfo.get( "do.what" ) ); } // Run tests on MTypes which have not been subscribed to. { String dummyMtype = "not.an.mtype"; Message dummyMsg = new Message( dummyMtype ); // Check the hub does not report clients are subscribed to MTypes // they are not subscribed to. Set subscribed = c3.getSubscribedClients( dummyMtype ).keySet(); assertTrue( ! subscribed.contains( id1 ) ); assertTrue( ! subscribed.contains( id2 ) ); // Send a message using notify to an unsubscribed recipient. // This should result in an error. try { c3.notify( id1, dummyMsg ); fail(); } catch ( SampException e ) { } // Send a message using call to an unsubscribed recipient. // This should result in an error. try { c3.call( id1, "xxx", dummyMsg ); fail(); } catch ( SampException e ) { } // Send a message using callAndWait to an unsubscribed recipient. // This should result in an error. try { c3.callAndWait( id1, dummyMsg, 0 ); fail(); } catch ( SampException e ) { } // Send message using notifyAll and callAll to which nobody is // subscribed. Nobody will receive this, but it is not an error. List notifyList = c3.notifyAll( dummyMsg ); assertEquals( 0, notifyList.size() ); Map callMap = c3.callAll( "yyy", dummyMsg ); assertEquals( 0, callMap.size() ); } // Check that hub event messages arrived concerning client 0 which // we registered and unregistered earlier. Do it here to give // messages enough time to have arrived; SAMP offers no guarantees // of delivery sequence, but if they haven't showed up yet it's // very likely that they never will. Throwable cwError = clientWatcher_.getError(); if ( cwError != null ) { throw new TestException( "Error encountered during hub event " + "processing", cwError ); } WatchedClient client0 = clientWatcher_.getClient( id0 ); assertTrue( client0 != null ); assertTrue( client0.reg_ ); assertTrue( client0.unreg_ ); assertEquals( meta0, client0.meta_ ); assertEquals( subs0, client0.subs_ ); // Check that the client watcher has received hub event messages // concerning itself as well. String cwId = clientWatcher_.getConnection().getRegInfo().getSelfId(); WatchedClient cwClient = clientWatcher_.getClient( cwId ); assertTrue( cwClient != null ); assertTrue( ! cwClient.unreg_ ); assertEquals( clientWatcher_.getMetadata(), cwClient.meta_ ); assertEquals( clientWatcher_.getSubscriptions(), cwClient.subs_ ); // Tidy up. c3.unregister(); assertTestClients( c1, new String[] { id2, } ); assertTestClients( c2, new String[] { id1, } ); c1.unregister(); c2.unregister(); } /** * Runs a lot of clients throwing a lot of messages at each other * simultaneously. */ private void testStress() throws IOException { ClientProfile profile = new ClientProfile() { public boolean isHubRunning() { return HubTester.this.profile_.isHubRunning(); } public HubConnection register() throws SampException { return HubTester.this.register(); } }; new CalcStorm( profile, random_, 10, 20, Calculator.RANDOM_MODE ) .run(); } /** * Assert that the given list of registered clients has a certain content. * * @param conn connection from which to call getRegisteredClients * @param otherIds array of client public IDs that getRegisteredClients * should return - will not contain ID associated with * conn itself */ private void assertTestClients( HubConnection conn, String[] otherIds ) throws IOException { // Call getRegisteredClients. Set knownOtherIds = new HashSet( Arrays.asList( conn.getRegisteredClients() ) ); // Remove from the list any clients which were already registered // before this test instance started up. for ( int ic = 0; ic < ignoreClients_.length; ic++ ) { String id = ignoreClients_[ ic ].getId(); knownOtherIds.remove( ignoreClients_[ ic ].getId() ); } // Assert that the (unordered) set retrieved is the same as that // asked about. assertEquals( knownOtherIds, new HashSet( Arrays.asList( otherIds ) ) ); } /** * Generates an object with random content for transmission using SAMP. * This may be a structure containing strings, lists and maps with * any legal values as defined by the SAMP data encoding rules. * * @param level maximum level of nesting (how deeply lists/maps * may appear within other lists/maps) * @param ugly if true, any legal SAMP content will be used; * if false, the returned object should be reasonably * human-readable if printed (toString) * @return random SAMP object */ public Object createRandomObject( int level, boolean ugly ) { if ( level == 0 ) { return createRandomString( ugly ); } int type = random_.nextInt( 2 ); if ( type == 0 ) { int nel = random_.nextInt( ugly ? 23 : 3 ); List list = new ArrayList( nel ); for ( int i = 0; i < nel; i++ ) { list.add( createRandomObject( level - 1, ugly ) ); } SampUtils.checkList( list ); return list; } else if ( type == 1 ) { int nent = random_.nextInt( ugly ? 23 : 3 ); Map map = new HashMap( nent ); for ( int i = 0; i < nent; i++ ) { map.put( createRandomString( ugly ), createRandomObject( level - 1, ugly ) ); } SampUtils.checkMap( map ); return map; } else { throw new AssertionError(); } } /** * Creates a new random string for transmission using SAMP. * This may have any legal content according to the SAMP data encoding * rules. * * @param ugly if true, any legal SAMP content will be used; * if false, the returned object should be reasonably * human-readable if printed (toString) */ public String createRandomString( boolean ugly ) { int nchar = random_.nextInt( ugly ? 99 : 4 ); StringBuffer sbuf = new StringBuffer( nchar ); char[] chrs = ugly ? GENERAL_CHARS : ALPHA_CHARS; for ( int i = 0; i < nchar; i++ ) { sbuf.append( chrs[ random_.nextInt( chrs.length ) ] ); } String str = sbuf.toString(); SampUtils.checkString( str ); return str; } /** * Waits for a given number of milliseconds. * * @param millis number of milliseconds */ private static void delay( int millis ) { Object lock = new Object(); try { synchronized ( lock ) { lock.wait( millis ); } } catch ( InterruptedException e ) { throw new RuntimeException( "Interrupted", e ); } } /** * Returns a character array containing each distinct alphanumeric * character. * * @return array of alphanumeric characters */ private static char[] createAlphaCharacters() { StringBuffer sbuf = new StringBuffer(); for ( char c = 'A'; c <= 'Z'; c++ ) { sbuf.append( c ); } for ( char c = '0'; c <= '9'; c++ ) { sbuf.append( c ); } return sbuf.toString().toCharArray(); } /** * Returns a character array containing every character which is legal * for inclusion in a SAMP string. * * @return array of string characters */ private static char[] createGeneralCharacters() { StringBuffer sbuf = new StringBuffer(); sbuf.append( (char) 0x09 ); sbuf.append( (char) 0x0a ); // Character 0x0d is problematic. Although it is permissible to // transmit this in an XML document, it can get transformed to // 0x0a or (if adjacent to an existing 0x0a) elided. // The correct thing to do probably would be to note in the standard // that all bets are off when transmitting line end characters - // but sending a line-end will probably end up as a line-end. // However I can't be bothered to start up a new thread about this // on the apps-samp list, so for the purposes of this test just // avoid sending it. // sbuf.append( (char) 0x0d ); for ( char c = 0x20; c <= 0x7f; c++ ) { sbuf.append( c ); } return sbuf.toString().toCharArray(); } /** * Main method. Tests a hub which is currently running. */ public static void main( String[] args ) throws IOException { int status = runMain( args ); if ( status != 0 ) { System.exit( status ); } } /** * Does the work for the main method. * Use -help flag. */ public static int runMain( String[] args ) throws IOException { String usage = new StringBuffer() .append( "\n Usage:" ) .append( "\n " ) .append( HubTester.class.getName() ) .append( "\n " ) .append( " [-help]" ) .append( " [-/+verbose]" ) .append( "\n " ) .append( " [-gui]" ) .append( "\n" ) .toString(); List argList = new ArrayList( Arrays.asList( args ) ); boolean gui = false; int verbAdjust = 0; for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.equals( "-gui" ) ) { it.remove(); gui = true; } else if ( arg.equals( "-nogui" ) ) { it.remove(); gui = false; } else if ( arg.startsWith( "-v" ) ) { it.remove(); verbAdjust--; } else if ( arg.startsWith( "+v" ) ) { it.remove(); verbAdjust++; } else if ( arg.startsWith( "-h" ) ) { it.remove(); System.out.println( usage ); return 0; } else { it.remove(); System.err.println( usage ); return 1; } } assert argList.isEmpty(); // Adjust logging in accordance with verboseness flags. int logLevel = Level.WARNING.intValue() + 100 * verbAdjust; Logger.getLogger( "org.astrogrid.samp" ) .setLevel( Level.parse( Integer.toString( logLevel ) ) ); // Get profile. ClientProfile profile = DefaultClientProfile.getProfile(); // Set up GUI monitor if required. JFrame frame; if ( gui ) { frame = new JFrame( "HubTester Monitor" ); frame.getContentPane().add( new HubMonitor( profile, true, 1 ) ); frame.pack(); frame.setVisible( true ); } else { frame = null; } new HubTester( profile ).run(); if ( frame != null ) { frame.dispose(); } return 0; } /** * CallableClient implementation for testing. */ private static class TestCallableClient extends ReplyCollector implements CallableClient { private final HubConnection connection_; private int pingCount_; public static final Subscriptions SUBS = getSubscriptions(); /** * Constructor. * * @param connection hub connection */ TestCallableClient( HubConnection connection ) { super( connection ); connection_ = connection; } public void receiveNotification( String senderId, Message msg ) { processCall( senderId, msg ); } public void receiveCall( String senderId, String msgId, Message msg ) throws SampException { // If the message contains a WAITMILLIS_KEY entry, interpret this // as a number of milliseconds to wait before the response is // sent back to the hub. String swaitMillis = (String) msg.get( WAITMILLIS_KEY ); if ( swaitMillis != null ) { int waitMillis = SampUtils.decodeInt( swaitMillis ); if ( waitMillis > 0 ) { delay( waitMillis ); } } Response response; // Process a FAIL_MTYPE message specially. if ( msg.getMType().equals( FAIL_MTYPE ) ) { Map errs = (Map) msg.getParam( ERROR_KEY ); if ( errs == null ) { throw new IllegalArgumentException(); } response = Response.createErrorResponse( new ErrInfo( errs ) ); } // For other MTypes, pass them to the processCall method. else { try { response = Response .createSuccessResponse( processCall( senderId, msg ) ); } catch ( Throwable e ) { response = Response.createErrorResponse( new ErrInfo( e ) ); } } // Insert the message ID into the response if requested to do so. String msgIdQuery = (String) msg.get( MSGIDQUERY_KEY ); if ( msgIdQuery != null && SampUtils.decodeBoolean( msgIdQuery ) ) { response.put( MSGIDQUERY_KEY, msgId ); } response.check(); // Return the reply, whatever it is, to the hub. connection_.reply( msgId, response ); } /** * Do the work of responding to a given SAMP message. * * @param senderId sender public ID * @param msg message object * @return content of the successful reply's samp.result entry */ private Map processCall( String senderId, Message msg ) { String mtype = msg.getMType(); // Returns the samp.params entry as the samp.result entry. if ( ECHO_MTYPE.equals( mtype ) ) { return msg.getParams(); } // Just bumps a counter and returns an empty samp.result else if ( PING_MTYPE.equals( mtype ) ) { synchronized ( this ) { pingCount_++; } return new HashMap(); } // Shouldn't happen. else { throw new TestException( "Unsubscribed MType? " + mtype ); } } /** * Returns the subscriptions object for this client. * * @return subscriptions */ private static Subscriptions getSubscriptions() { Subscriptions subs = new Subscriptions(); subs.addMType( ECHO_MTYPE ); subs.addMType( PING_MTYPE ); subs.addMType( FAIL_MTYPE ); subs.check(); return subs; } } /** * CallableClient implementation which watches hub.event messages * concerning the registration and attributes of other clients. */ private static class ClientWatcher implements CallableClient { private final HubConnection connection_; private final Map clientMap_; private Throwable error_; /** * Constructor. * * @param connection hub connection */ ClientWatcher( HubConnection connection ) { connection_ = connection; clientMap_ = Collections.synchronizedMap( new HashMap() ); } /** * Returns a WatchedClient object corresponding to a given client * public ID. This will contain information about the hub event * messages this watcher has received concerning that client up till * now. * * @param id public id of a client which has been registered * @return watchedClient object if any messages have been received * about id, otherwise null */ public WatchedClient getClient( String id ) { return (WatchedClient) clientMap_.get( id ); } /** * Returns an error if any error has been thrown during processing * of hub event messages. * * @return deferred throwable, or null in case of no problems */ public Throwable getError() { return error_; } /** * Returns the hub connection used by this client. * * @return hub connection */ public HubConnection getConnection() { return connection_; } public void receiveCall( String senderId, String msgId, Message msg ) { receiveNotification( senderId, msg ); Response response = error_ == null ? Response.createSuccessResponse( new HashMap() ) : Response.createErrorResponse( new ErrInfo( "broken" ) ); try { connection_.reply( msgId, response ); } catch ( SampException e ) { error_ = e; } } public void receiveNotification( String senderId, Message msg ) { if ( error_ == null ) { try { processMessage( senderId, msg ); } catch ( Throwable e ) { error_ = e; } } } private void processMessage( String senderId, Message msg ) throws IOException { // Check the message actually comes from the hub. assertEquals( senderId, connection_.getRegInfo().getHubId() ); String mtype = msg.getMType(); Map params = msg.getParams(); // Get (if necessary lazily creating) a WatchedClient object // which this message concerns. String id = (String) msg.getParam( "id" ); assertTrue( id != null ); synchronized ( clientMap_ ) { if ( ! clientMap_.containsKey( id ) ) { clientMap_.put( id, new WatchedClient() ); } WatchedClient client = (WatchedClient) clientMap_.get( id ); // Handle the various hub event messages by updating fields of // the right WatchedClient object. if ( REGISTER_MTYPE.equals( mtype ) ) { assertTrue( ! client.reg_ ); client.reg_ = true; } else if ( UNREGISTER_MTYPE.equals( mtype ) ) { assertTrue( ! client.unreg_ ); client.unreg_ = true; } else if ( METADATA_MTYPE.equals( mtype ) ) { assertTrue( params.containsKey( "metadata" ) ); Metadata meta = Metadata .asMetadata( (Map) params.get( "metadata" ) ); meta.check(); client.meta_ = meta; } else if ( SUBSCRIPTIONS_MTYPE.equals( mtype ) ) { assertTrue( params.containsKey( "subscriptions" ) ); Subscriptions subs = Subscriptions .asSubscriptions( (Map) params.get( "subscriptions" ) ); subs.check(); client.subs_ = subs; } else { fail(); } clientMap_.notifyAll(); } } public void receiveResponse( String responderId, String msgTag, Response response ) { throw new UnsupportedOperationException(); } /** * Returns a suitable subscriptions object for this client. * * @return subscriptions */ public static Subscriptions getSubscriptions() { Subscriptions subs = new Subscriptions(); subs.addMType( REGISTER_MTYPE ); subs.addMType( UNREGISTER_MTYPE ); subs.addMType( METADATA_MTYPE ); subs.addMType( SUBSCRIPTIONS_MTYPE ); subs.check(); return subs; } /** * Returns a suitable metadata object for this client. */ public static Metadata getMetadata() { Metadata meta = new Metadata(); meta.setName( "ClientWatcher" ); meta.setDescriptionText( "Tracks other clients for HubTester" ); meta.check(); return meta; } } /** * Struct-type utility class which aggregates mutable information about * a client, to be updated in response to hub event messages. */ private static class WatchedClient { /** Whether this client has ever been registered. */ boolean reg_; /** Whether this clent has ever been unregistered. */ boolean unreg_; /** Current metadata object for this client. */ Metadata meta_; /** Current subscriptions object for this client. */ Subscriptions subs_; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/MessageSender.java000066400000000000000000000552321356450004300255570ustar00rootroot00000000000000package org.astrogrid.samp.test; import java.io.IOException; import java.io.PrintStream; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * Sends a message to one or more other SAMP clients. * Intended for use from the command line. * * @author Mark Taylor * @since 23 Jul 2008 */ public abstract class MessageSender { private static Logger logger_ = Logger.getLogger( MessageSender.class.getName() ); /** * Sends a message to a given list of recipients. * If recipientIds is null, then will be sent to all * subscribed clients. * * @param connection hub connection * @param msg message to send * @param recipientIds array of recipients to target, or null * @return responder Client -> Response map */ abstract Map getResponses( HubConnection connection, Message msg, String[] recipientIds ) throws IOException; /** * Sends a message to a list of recipients and displays the results * on an output stream. * * @param connection hub connection * @param msg message to send * @param recipientIds array of recipients to target, or null * @param destination print stream */ void showResults( HubConnection connection, Message msg, String[] recipientIds, PrintStream out ) throws IOException { Map responses = getResponses( connection, msg, recipientIds ); for ( Iterator it = responses.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); String responderId = (String) entry.getKey(); Client responder = new MetaClient( responderId, connection ); Object response = entry.getValue(); out.println(); out.println( responder ); if ( response instanceof Throwable ) { ((Throwable) response).printStackTrace( out ); } else { out.println( SampUtils.formatObject( response, 3 ) ); } } } /** * Translates an array of client names to client IDs. * If some or all cannot be identified, an exception is thrown. * * @param conn hub connection * @param names array of client names, interpreted case-insensitively * @return array of client ids corresponding to names */ private static String[] namesToIds( HubConnection conn, String[] names ) throws SampException { int count = names.length; if ( count == 0 ) { return new String[ 0 ]; } String[] ids = new String[ count ]; BitSet flags = new BitSet( count ); String[] allIds = conn.getRegisteredClients(); for ( int ic = 0; ic < allIds.length; ic++ ) { String id = allIds[ ic ]; Metadata meta = conn.getMetadata( id ); String name = meta.getName(); for ( int in = 0; in < count; in++ ) { if ( names[ in ].equalsIgnoreCase( name ) ) { ids[ in ] = id; flags.set( in ); } } if ( flags.cardinality() == count ) { return ids; } } assert flags.cardinality() < count; List unknownList = new ArrayList(); for ( int in = 0; in < count; in++ ) { if ( ids[ in ] == null ) { unknownList.add( names[ in ] ); } } throw new SampException( "Unknown client " + ( unknownList.size() == 1 ? ( "name " + unknownList.get( 0 ) ) : ( "names " + unknownList ) ) ); } /** * Main method. * Use -help flag for documentation. */ public static void main( String[] args ) throws IOException { int status = runMain( args ); if ( status != 0 ) { System.exit( status ); } } /** * Does the work for the main method. */ public static int runMain( String[] args ) throws IOException { // Assemble usage string. String usage = new StringBuffer() .append( "\n Usage:" ) .append( "\n " + MessageSender.class.getName() ) .append( "\n " ) .append( " [-help]" ) .append( " [-/+verbose]" ) .append( "\n " ) .append( " -mtype " ) .append( " [-param ...]" ) .append( " [-mode sync|async|notify]" ) .append( "\n " ) .append( " [-targetid ...]" ) .append( " [-targetname ...]" ) .append( "\n " ) .append( " [-sendername ]" ) .append( " [-sendermeta ]" ) .append( "\n" ) .toString(); // Set up variables which can be set or changed by the argument list. String mtype = null; List targetIdList = new ArrayList(); List targetNameList = new ArrayList(); Map paramMap = new HashMap(); String mode = "sync"; Metadata meta = new Metadata(); int timeout = 0; int verbAdjust = 0; // Parse the argument list. List argList = new ArrayList( Arrays.asList( args ) ); for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.equals( "-mtype" ) && it.hasNext() ) { it.remove(); if ( mtype != null ) { System.err.println( usage ); return 1; } mtype = (String) it.next(); it.remove(); } else if ( arg.equals( "-targetid" ) && it.hasNext() ) { it.remove(); targetIdList.add( (String) it.next() ); it.remove(); } else if ( arg.equals( "-targetname" ) && it.hasNext() ) { it.remove(); targetNameList.add( (String) it.next() ); it.remove(); } else if ( arg.equals( "-param" ) && it.hasNext() ) { it.remove(); String pName = (String) it.next(); it.remove(); String pValue; if ( it.hasNext() ) { pValue = (String) it.next(); it.remove(); } else { System.err.println( usage ); return 1; } paramMap.put( pName, SampUtils.parseValue( pValue ) ); } else if ( arg.equals( "-mode" ) && it.hasNext() ) { it.remove(); mode = (String) it.next(); it.remove(); } else if ( arg.equals( "-sendername" ) && it.hasNext() ) { it.remove(); meta.setName( (String) it.next() ); it.remove(); } else if ( arg.equals( "-sendermeta" ) && it.hasNext() ) { it.remove(); String mName = (String) it.next(); it.remove(); String mValue; if ( it.hasNext() ) { mValue = (String) it.next(); it.remove(); } else { System.err.println( usage ); return 1; } meta.put( mName, SampUtils.parseValue( mValue ) ); } else if ( arg.equals( "-timeout" ) && it.hasNext() ) { it.remove(); String stimeout = (String) it.next(); it.remove(); try { timeout = Integer.parseInt( stimeout ); } catch ( NumberFormatException e ) { System.err.println( "Not numeric: " + stimeout ); System.err.println( usage ); return 1; } } else if ( arg.startsWith( "-v" ) ) { it.remove(); verbAdjust--; } else if ( arg.startsWith( "+v" ) ) { it.remove(); verbAdjust++; } else if ( arg.startsWith( "-h" ) ) { it.remove(); System.out.println( usage ); return 0; } else { it.remove(); System.err.println( usage ); return 1; } } if ( ! argList.isEmpty() ) { System.err.println( usage ); return 1; } if ( mtype == null ) { System.err.println( usage ); return 1; } // Set logging levels in accordance with flags. int logLevel = Level.WARNING.intValue() + 100 * verbAdjust; Logger.getLogger( "org.astrogrid.samp" ) .setLevel( Level.parse( Integer.toString( logLevel ) ) ); // Get profile. ClientProfile profile = DefaultClientProfile.getProfile(); // Create a message sender object. final MessageSender sender; if ( mode.toLowerCase().startsWith( "async" ) ) { sender = new AsynchSender(); } else if ( mode.toLowerCase().startsWith( "sync" ) ) { sender = new SynchSender( timeout ); } else if ( mode.toLowerCase().startsWith( "notif" ) ) { sender = new NotifySender(); } else { System.err.println( usage ); return 1; } // Prepare to send the message. Message msg = new Message( mtype, paramMap ); // Register. HubConnection connection = profile.register(); if ( connection == null ) { System.err.println( "No hub is running" ); return 1; } connection.declareMetadata( meta ); // Assemble target list. String[] targetNames = (String[]) targetNameList.toArray( new String[ 0 ] ); targetIdList.addAll( Arrays.asList( namesToIds( connection, targetNames ) ) ); String[] targets = targetIdList.isEmpty() ? null : (String[]) targetIdList.toArray( new String[ 0 ] ); // Send the message, displaying the results on System.out. sender.showResults( connection, msg, targets, System.out ); // Tidy up and exit. connection.unregister(); return 0; } /** * MessageSender implementation which uses the Notify pattern. */ private static class NotifySender extends MessageSender { public Map getResponses( HubConnection connection, Message msg, String[] recipientIds ) throws IOException { final List recipientList; if ( recipientIds == null ) { recipientList = connection.notifyAll( msg ); if ( recipientList.size() == 0 ) { logger_.warning( "No clients subscribed to " + msg.getMType() ); } } else { for ( int ir = 0; ir < recipientIds.length; ir++ ) { connection.notify( recipientIds[ ir ], msg ); } recipientList = Arrays.asList( recipientIds ); } Map responseMap = new HashMap(); for ( Iterator it = recipientList.iterator(); it.hasNext(); ) { responseMap.put( it.next(), "" ); } return responseMap; } } /** * MessageSender implementation which uses the Synchronous Call/Response * pattern. */ private static class SynchSender extends MessageSender { private final int timeout_; /** * Constructor. * * @param timeout in seconds */ SynchSender( int timeout ) { timeout_ = timeout; } public Map getResponses( final HubConnection connection, final Message msg, String[] recIds ) throws IOException { final String[] recipientIds = recIds == null ? (String[]) connection.getSubscribedClients( msg.getMType() ) .keySet().toArray( new String[ 0 ] ) : recIds; if ( recipientIds.length == 0 ) { logger_.warning( "No clients subscribed to " + msg.getMType() ); return new HashMap(); } else { int nsend = recipientIds.length; logger_.log( nsend == 0 ? Level.WARNING : Level.INFO, "Waiting for " + nsend + " responses" ); } final BlockingMap map = new BlockingMap(); for ( int ir = 0; ir < recipientIds.length; ir++ ) { final String id = recipientIds[ ir ]; new Thread() { public void run() { Object result; try { result = connection.callAndWait( id, msg, timeout_ ); } catch ( Throwable e ) { result = e; } map.put( id, result ); if ( map.size() >= recipientIds.length ) { map.done(); } } }.start(); } return map; } } /** * MessageSender implementation which uses the Asynchronous Call/Response * pattern. */ private static class AsynchSender extends MessageSender { private int iseq_; public Map getResponses( HubConnection connection, Message msg, String[] recipientIds ) throws IOException { String msgTag = "tag-" + ++iseq_; Collector collector = new Collector(); // Sets the connection's callable client to a new object. // Since the Standard Profile doesn't say it's OK to do this // more than once, this means that this it is not really safe // to call getResponses more than once for this object. connection.setCallable( collector ); if ( recipientIds == null ) { Set recipientSet = connection.callAll( msgTag, msg ).keySet(); collector.setRecipients( recipientSet ); } else { Set recipientSet = new HashSet( Arrays.asList( recipientIds ) ); for ( Iterator it = recipientSet.iterator(); it.hasNext(); ) { String recipientId = (String) it.next(); connection.call( recipientId, msgTag, msg ); } collector.setRecipients( recipientSet ); } return collector.map_; } /** * CallableClient implementation which collects asynchronous message * responses. */ private static class Collector implements CallableClient { final BlockingMap map_; Collection recipients_; /** * Constructor. * * @param nExpected number of responses expected by this collector */ Collector() { map_ = new BlockingMap(); } /** * Notifies this object which clients it should expect a response * from. Must be called at some point, or the returned map's * iterator will block indefinitely. * * @param recipients set of client ids for expected responders */ public void setRecipients( Collection recipients ) { int nsend = recipients.size(); logger_.log( nsend == 0 ? Level.WARNING : Level.INFO, "Waiting for " + nsend + " responses" ); recipients_ = recipients; if ( map_.keySet().containsAll( recipients_ ) ) { map_.done(); } } public void receiveCall( String senderId, String msgId, Message msg ) { throw new UnsupportedOperationException(); } public void receiveNotification( String senderId, Message msg ) { throw new UnsupportedOperationException(); } public void receiveResponse( String responderId, String msgTag, Response response ) { map_.put( responderId, response ); if ( recipients_ != null && map_.keySet().containsAll( recipients_ ) ) { map_.done(); } } } } /** * Client implementation which may know about metadata. */ private static class MetaClient implements Client { private final String id_; private final Metadata meta_; /** * Constructor which attempts to acquire metadata from a given * hub connection. * * @param client id * @param connection hub connection */ public MetaClient( String id, HubConnection connection ) throws SampException { this( id, connection.getMetadata( id ) ); } /** * Constructor which uses supplied metadata. * * @param id client id * @param meta metadata (may be null) */ public MetaClient( String id, Metadata meta ) { id_ = id; meta_ = meta; } public String getId() { return id_; } public Metadata getMetadata() { return meta_; } public Subscriptions getSubscriptions() { return null; } public String toString() { StringBuffer sbuf = new StringBuffer(); sbuf.append( getId() ); String name = meta_ == null ? null : meta_.getName(); if ( name != null ) { sbuf.append( " (" ) .append( name ) .append( ')' ); } return sbuf.toString(); } } /** * Map implementation which dispenses its contents via an iterator * which will block until all the results are in. This makes it * suitable for use from other threads. */ private static class BlockingMap extends AbstractMap { private final BlockingSet entrySet_; /** * Constructor. */ BlockingMap() { entrySet_ = new BlockingSet(); } public Set entrySet() { return entrySet_; } public synchronized Object put( final Object key, final Object value ) { entrySet_.add( new Map.Entry() { public Object getKey() { return key; } public Object getValue() { return value; } public Object setValue( Object value ) { throw new UnsupportedOperationException(); } } ); return null; } /** * Indicates that no more entries will be added to this map. * Must be called by populator or entry set iterator will block * indefinitely. */ synchronized void done() { entrySet_.done(); } } /** * Set implementation which dispenses its contents via an iterator * which will block until all results are in. */ private static class BlockingSet extends AbstractSet { private final List list_; private boolean done_; /** * Constructor. */ BlockingSet() { list_ = Collections.synchronizedList( new ArrayList() ); } public boolean add( Object o ) { assert ! list_.contains( o ); synchronized ( list_ ) { list_.add( o ); list_.notifyAll(); } return true; } /** * Indicates that no more items will be added to this set. * Must be called by populator or iterator will block * indefinitely. */ public void done() { done_ = true; synchronized ( list_ ) { list_.notifyAll(); } } public int size() { return list_.size(); } public Iterator iterator() { return new Iterator() { int index_; public void remove() { throw new UnsupportedOperationException(); } public Object next() { return list_.get( index_++ ); } public boolean hasNext() { synchronized ( list_ ) { while ( index_ >= list_.size() && ! done_ ) { try { list_.wait(); } catch ( InterruptedException e ) { throw new RuntimeException( "Interrupted", e ); } } return index_ < list_.size(); } } }; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/ReplyCollector.java000066400000000000000000000166511356450004300257760ustar00rootroot00000000000000package org.astrogrid.samp.test; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.astrogrid.samp.Response; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * Partial implementation of {@link CallableClient} which handles the * receiveReply method. * This takes care of matching up replies with calls and is intended for * use with test classes. Some assertions are made within this class * to check that replies match with messages sent. Call-type messages * must be sent using this object's call and callAll * methods, rather than directly on the HubConnection, * to ensure that the internal state stays correct. * * @author Mark Taylor * @since 18 Jul 2008 */ abstract class ReplyCollector implements CallableClient { private final HubConnection connection_; private final Set sentSet_; private final Map replyMap_; private boolean allowTagReuse_; /** * Constructor. * * @param connection hub connection * @param allowTagReuse if true clients may reuse tags; * if false any such attempt generates an exception */ public ReplyCollector( HubConnection connection ) { connection_ = connection; sentSet_ = Collections.synchronizedSet( new HashSet() ); replyMap_ = Collections.synchronizedMap( new HashMap() ); } /** * Determines whether clients are permitted to reuse tags for different * messages. If true, any such attempt generates an exception. * * @param allow whether to allow tag reuse */ public void setAllowTagReuse( boolean allow ) { allowTagReuse_ = allow; } /** * Performs a call method on this collector's hub connection. * Additional internal state is updated. * Although it is legal as far as SAMP goes, the msgTag * must not be one which was used earlier for the same recipient. * * @param recipientId public-id of client to receive message * @param msgTag arbitrary string tagging this message for caller's * benefit * @param msg {@link org.astrogrid.samp.Message}-like map * @return message ID */ public String call( String recipientId, String msgTag, Map msg ) throws SampException { Object key = createKey( recipientId, msgTag ); if ( ! allowTagReuse_ && sentSet_.contains( key ) ) { throw new IllegalArgumentException( "Key " + key + " reused" ); } sentSet_.add( key ); return connection_.call( recipientId, msgTag, msg ); } /** * Performs a callAll method on this collector's * hub connection. * Additional internal state is updated. * Although it is legal as far as SAMP goes, the msgTag * must not be one which was used for an earlier broadcast. * * @param msgTag arbitrary string tagging this message for caller's * benefit * @param msg {@link org.astrogrid.samp.Message}-like map * @return message ID */ public Map callAll( String msgTag, Map msg ) throws SampException { Object key = createKey( null, msgTag ); if ( ! allowTagReuse_ && sentSet_.contains( key ) ) { throw new IllegalArgumentException( "Key " + key + " reused" ); } sentSet_.add( key ); return connection_.callAll( msgTag, msg ); } public void receiveResponse( String responderId, String msgTag, Response response ) { Object key = createKey( responderId, msgTag ); Object result; try { if ( ! allowTagReuse_ && replyMap_.containsKey( key ) ) { throw new TestException( "Response for " + key + " already received" ); } else if ( ! sentSet_.contains( key ) && ! sentSet_.contains( createKey( null, msgTag ) ) ) { throw new TestException( "Message " + key + " never sent" ); } result = response; } catch ( TestException e ) { result = e; } synchronized ( replyMap_ ) { if ( ! replyMap_.containsKey( key ) ) { replyMap_.put( key, new ArrayList() ); } ((List) replyMap_.get( key )).add( result ); replyMap_.notifyAll(); } } /** * Returns the total number of unretrieved replies so far collected by * this object. * * @return reply count */ public int getReplyCount() { int count = 0; synchronized ( replyMap_ ) { for ( Iterator it = replyMap_.values().iterator(); it.hasNext(); ) { count += ((List) it.next()).size(); } } return count; } /** * Waits for a reply to a message sent earlier * using call or callAll. * Blocks until such a response is received. * * @param responderId client ID of client providing response * @param msgTag tag which was used to send the message * @return response */ public Response waitForReply( String responderId, String msgTag ) { Object key = createKey( responderId, msgTag ); try { synchronized ( replyMap_ ) { while ( ! replyMap_.containsKey( key ) || ((List) replyMap_.get( key )).isEmpty() ) { replyMap_.wait(); } } } catch ( InterruptedException e ) { throw new Error( "Interrupted", e ); } return getReply( responderId, msgTag ); } /** * Gets the reply to a message sent earlier * using call or callAll. * Does not block; if no such response has been received so far, * returns null. * * @param responderId client ID of client providing response * @param msgTag tag which was used to send the message * @return response */ public Response getReply( String responderId, String msgTag ) { Object key = createKey( responderId, msgTag ); synchronized ( replyMap_ ) { List list = (List) replyMap_.get( key ); Object result = list == null || list.isEmpty() ? null : list.remove( 0 ); if ( result == null ) { return null; } else if ( result instanceof Response ) { return (Response) result; } else if ( result instanceof Throwable ) { throw new TestException( (Throwable) result ); } else { throw new AssertionError(); } } } /** * Returns an opaque object suitable for use as a map key * based on a recipient ID and message tag. * * @param recipientId recipient ID * @param msgTag message tag */ private static Object createKey( String recipientId, String msgTag ) { return Arrays.asList( new String[] { recipientId, msgTag } ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/Snooper.java000066400000000000000000000253141356450004300244550ustar00rootroot00000000000000package org.astrogrid.samp.test; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.Client; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.HubConnector; import org.astrogrid.samp.client.MessageHandler; import org.astrogrid.samp.httpd.UtilServer; /** * Subscribes to SAMP messages and logs any received to an output stream. * The only responses to messages have samp.status=samp.warning. * * @author Mark Taylor * @since 4 Sep 2008 */ public class Snooper { private final OutputStream out_; private final Map clientMap_; private static final byte[] newline_; static { byte[] nl; try { nl = System.getProperty( "line.separator", "\n" ) .getBytes( "UTF-8" ); } catch ( Exception e ) { nl = new byte[] { (byte) '\n' }; } newline_ = nl; } private static final Logger logger_ = Logger.getLogger( Snooper.class.getName() ); /** * Constructor using default metadata. * * @param profile profile * @param subs subscriptions defining which messages are received * and logged * @param out destination stream for logging info * @param autoSec number of seconds between auto connection attempts */ public Snooper( ClientProfile profile, Subscriptions subs, OutputStream out, int autoSec ) { this( profile, subs, createDefaultMetadata(), out, autoSec ); } /** * Constructor using custom metadata. * * @param profile profile * @param subs subscriptions defining which messages are received * and logged * @param meta client metadata * @param out destination stream for logging info * @param autoSec number of seconds between auto connection attempts */ public Snooper( ClientProfile profile, final Subscriptions subs, Metadata meta, OutputStream out, int autoSec ) { HubConnector connector = new HubConnector( profile ); connector.declareMetadata( meta ); out_ = out; // Prepare all-purpose response to logged messages. final Response response = new Response(); response.setStatus( Response.WARNING_STATUS ); response.setResult( new HashMap() ); response.setErrInfo( new ErrInfo( "Message logged, not acted on" ) ); // Add a handler which will handle the subscribed messages. connector.addMessageHandler( new MessageHandler() { public Map getSubscriptions() { return subs; } public void receiveNotification( HubConnection connection, String senderId, Message msg ) throws IOException { log( senderId, msg, null ); } public void receiveCall( HubConnection connection, String senderId, String msgId, Message msg ) throws IOException { log( senderId, msg, msgId ); connection.reply( msgId, response ); } } ); connector.declareSubscriptions( connector.computeSubscriptions() ); clientMap_ = connector.getClientMap(); // Connect and ready to log. connector.setActive( true ); connector.setAutoconnect( autoSec ); } /** * Logs a received message. * * @param senderId message sender public ID * @param msg message object * @param msgId message ID for call/response type messages * (null for notify type messages) */ private void log( String senderId, Message msg, String msgId ) throws IOException { StringBuffer sbuf = new StringBuffer(); sbuf.append( senderId ); Client client = (Client) clientMap_.get( senderId ); if ( client != null ) { Metadata meta = client.getMetadata(); if ( meta != null ) { String name = meta.getName(); if ( name != null ) { sbuf.append( " (" ) .append( name ) .append( ")" ); } } } sbuf.append( " --- " ); if ( msgId == null ) { sbuf.append( "notify" ); } else { sbuf.append( "call" ) .append( " (" ) .append( msgId ) .append( ")" ); } out_.write( newline_ ); out_.write( sbuf.toString().getBytes( "UTF-8" ) ); out_.write( newline_ ); out_.write( SampUtils.formatObject( msg, 3 ).getBytes( "UTF-8" ) ); out_.write( newline_ ); } /** * Returns the default metadata for the Snooper client. * * @return meta */ public static Metadata createDefaultMetadata() { Metadata meta = new Metadata(); meta.setName( "Snooper" ); meta.setDescriptionText( "Listens in to messages" + " for logging purposes" ); try { meta.setIconUrl( UtilServer.getInstance() .exportResource( "/org/astrogrid/samp/images/" + "ears.png" ) .toString() ); } catch ( IOException e ) { logger_.warning( "Can't export icon" ); } meta.put( "Author", "Mark Taylor" ); return meta; } /** * Main method. Runs a snooper. */ public static void main( String[] args ) throws IOException { int status = runMain( args ); if ( status != 0 ) { System.exit( status ); } } /** * Does the work for the main method. * Use -help flag. */ public static int runMain( String[] args ) throws IOException { String usage = new StringBuffer() .append( "\n Usage:" ) .append( "\n " ) .append( Snooper.class.getName() ) .append( "\n " ) .append( " [-help]" ) .append( " [-/+verbose]" ) .append( "\n " ) .append( " [-clientname ]" ) .append( " [-clientmeta ]" ) .append( "\n " ) .append( " [-mtype ]" ) .append( " [-subs ]" ) .append( "\n" ) .toString(); List argList = new ArrayList( Arrays.asList( args ) ); int verbAdjust = 0; Subscriptions subs = new Subscriptions(); Metadata meta = new Metadata(); for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.startsWith( "-mtype" ) && it.hasNext() ) { it.remove(); String mpat = (String) it.next(); it.remove(); subs.addMType( mpat ); } else if ( arg.equals( "-subs" ) && it.hasNext() ) { it.remove(); String mpat = (String) it.next(); it.remove(); String substr; if ( it.hasNext() ) { substr = (String) it.next(); it.remove(); } else { System.err.println( usage ); return 1; } Object subsInfo = SampUtils.fromJson( substr ); subs.put( mpat, subsInfo ); } else if ( arg.equals( "-clientname" ) && it.hasNext() ) { it.remove(); meta.setName( (String) it.next() ); it.remove(); } else if ( arg.equals( "-clientmeta" ) && it.hasNext() ) { it.remove(); String mName = (String) it.next(); it.remove(); String mValue; if ( it.hasNext() ) { mValue = (String) it.next(); it.remove(); } else { System.err.println( usage ); return 1; } Object mVal = SampUtils.parseValue( mValue ); if ( mVal == null ) { meta.remove( mName ); } else { meta.put( mName, mVal ); } } else if ( arg.startsWith( "-v" ) ) { it.remove(); verbAdjust--; } else if ( arg.startsWith( "+v" ) ) { it.remove(); verbAdjust++; } else if ( arg.startsWith( "-h" ) ) { it.remove(); System.out.println( usage ); return 0; } else { it.remove(); System.err.println( usage ); return 1; } } assert argList.isEmpty(); // Adjust logging in accordance with verboseness flags. int logLevel = Level.WARNING.intValue() + 100 * verbAdjust; Logger.getLogger( "org.astrogrid.samp" ) .setLevel( Level.parse( Integer.toString( logLevel ) ) ); // Combine custom and default metadata. Metadata m2 = createDefaultMetadata(); m2.putAll( meta ); meta = m2; // Set default subscriptions (everything) if none has been specified // explicitly. if ( subs.isEmpty() ) { subs.addMType( "*" ); } // Get profile. ClientProfile profile = DefaultClientProfile.getProfile(); // Start and run snooper. new Snooper( profile, subs, meta, System.out, 2 ); // Wait indefinitely. Object lock = new String( "Forever" ); synchronized( lock ) { try { lock.wait(); } catch ( InterruptedException e ) { } } return 0; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/TestException.java000066400000000000000000000007431356450004300256250ustar00rootroot00000000000000package org.astrogrid.samp.test; /** * Exception thrown by a failed test. * * @author Mark Taylor * @since 18 Jul 2008 */ public class TestException extends RuntimeException { public TestException() { super(); } public TestException( String msg ) { super( msg ); } public TestException( String msg, Throwable cause ) { super( msg, cause ); } public TestException( Throwable cause ) { super( cause ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/TestXmlrpcClient.java000066400000000000000000000101121356450004300262620ustar00rootroot00000000000000package org.astrogrid.samp.test; import java.io.InputStream; import java.io.IOException; import java.net.URL; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.xmlrpc.internal.InternalClient; import org.astrogrid.samp.xmlrpc.internal.XmlUtils; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; /** * SampXmlrpcClient implementation for testing success or failure of * XML-RPC method invocations. * Note the return value of the {@link #callAndWait} method is either * {@link #SUCCESS} or {@link #FAILURE}, rather than the actual result * of the call. * * Methods may throw {@link TestException}s to indicate assertion failures. * * @author Mark Taylor * @since 28 Aug 2009 */ class TestXmlrpcClient extends InternalClient { public static final Object SUCCESS = "Success"; public static final Object FAILURE = "Failure"; /** * Constructor. * * @param endpoint hub HTTP endpoint */ TestXmlrpcClient( URL endpoint ) { super( endpoint ); } /** * Returns {@link #SUCCESS} or {@link #FAILURE}. */ protected Object deserializeResponse( InputStream in ) throws IOException { try { Document doc = DocumentBuilderFactory.newInstance() .newDocumentBuilder().parse( in ); Element topEl = XmlUtils.getChild( doc, "methodResponse" ); Element contentEl = XmlUtils.getChild( topEl ); String contentTag = contentEl.getTagName(); if ( "params".equals( contentTag ) ) { Element paramEl = XmlUtils.getChild( contentEl, "param" ); Element valueEl = XmlUtils.getChild( paramEl, "value" ); Object value = XmlUtils.parseSampValue( valueEl ); SampUtils.checkObject( value ); return SUCCESS; } else if ( "fault".equals( contentTag ) ) { Element valueEl = XmlUtils.getChild( contentEl, "value" ); Map value = (Map) XmlUtils.parseSampValue( valueEl ); String faultString = (String) value.get( "faultString" ); int faultCode = ((Integer) value.get( "faultCode" )).intValue(); Tester.assertEquals( 2, value.size() ); return FAILURE; } else { throw new TestException( "Unknown child: " + contentTag ); } } catch ( ParserConfigurationException e ) { throw (IOException) new IOException( "Trouble with XML parsing" ) .initCause( e ); } catch ( SAXException e ) { throw (IOException) new IOException( "Trouble with XML parsing" ) .initCause( e ); } catch ( DOMException e ) { throw (IOException) new IOException( "Trouble with XML parsing" ) .initCause( e ); } } /** * Makes a call, and asserts that the result is a normal XML-RPC * response. * * @param method XML-RPC method name * @param params parameters for XML-RPC call (SAMP-compatible) * @throws TestException if the response is not a success */ public void checkSuccessCall( String method, List params ) throws IOException { Tester.assertEquals( SUCCESS, callAndWait( method, params ) ); } /** * Makes a call, and asserts that the result is an XML-RPC fault. * * @param method XML-RPC method name * @param params parameters for XML-RPC call (SAMP-compatible) * @throws TestException if the response is not a fault */ public void checkFailureCall( String method, List params ) throws IOException { Tester.assertEquals( FAILURE, callAndWait( method, params ) ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/Tester.java000066400000000000000000000032131356450004300242700ustar00rootroot00000000000000package org.astrogrid.samp.test; /** * No-frills test case superclass. * * @author Mark Taylor * @since 18 Jul 2008 */ public class Tester { /** * Fails a test. * * @throws TextException always */ public static void fail() throws TestException { throw new TestException( "Test failed" ); } /** * Tests an assertion. * * @param test asserted condition * @throws TestException if test is false */ public static void assertTrue( boolean test ) throws TestException { if ( ! test ) { throw new TestException( "Test failed" ); } } /** * Tests object equality. * * @param o1 object 1 * @param o2 object 2 * @throws TestException unless o1 and o2 * are both null or are equal in the sense of * {@link java.lang.Object#equals} */ public static void assertEquals( Object o1, Object o2 ) throws TestException { if ( o1 == null && o2 == null ) { } else if ( o1 == null || ! o1.equals( o2 ) ) { throw new TestException( "Test failed: " + o1 + " != " + o2 ); } } /** * Tests integer equality. * * @param i1 integer 1 * @param i2 integer 2 * @throws TestException iff i1 != i2 */ public static void assertEquals( int i1, int i2 ) throws TestException { if ( i1 != i2 ) { throw new TestException( "Test failed: " + i1 + " != " + i2 ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/test/package.html000066400000000000000000000004031356450004300244360ustar00rootroot00000000000000 Classes for testing. As well as unit testing of this SAMP toolkit, it includes the {@link org.astrogrid.samp.test.HubTester} class which tests a running third-party hub implementation and some miscellaneous diagnostic and utility applications. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/util/000077500000000000000000000000001356450004300221565ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/util/ResourceType.java000066400000000000000000000273131356450004300254600ustar00rootroot00000000000000package org.astrogrid.samp.util; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.Message; /** * Defines one of the types of resource that can be turned into a * SAMP load-type message. * * @author Mark Taylor * @since 21 Aug 2019 */ public abstract class ResourceType { private final String name_; private final String mtype_; private final String[] ctypes_; /** Resource type for table.load.votable. */ public static final ResourceType RTYPE_VOTABLE; /** Resource type for image.load.fits. */ public static final ResourceType RTYPE_FITS; /** Resource type for table.load.cdf. */ public static final ResourceType RTYPE_CDF; private static final ResourceType[] RESOURCE_TYPES = { RTYPE_VOTABLE = createVOTableResourceType(), RTYPE_FITS = createFitsImageResourceType(), RTYPE_CDF = createCdfTableResourceType(), }; private static final int MAGIC_SIZE = 1024; private static final Logger logger_ = Logger.getLogger( ResourceType.class.getName() ); /** * Constructor. * * @param name identifying name * @param mtype MType of message that will be sent * @param ctypes MIME-types to which this corresponds, * supplied in normalised form * (lower case, no parameters, no whitespace) */ public ResourceType( String name, String mtype, String[] ctypes ) { name_ = name; mtype_ = mtype; ctypes_ = ctypes; } /** * Returns the MType of the message to be constructed. * * @return MType string */ public String getMType() { return mtype_; } /** * Returns a Message object that will forward a given URL to SAMP * clients. * * @param url URL of resource * @return message instance */ public Message createMessage( URL url ) { Map params = new LinkedHashMap(); params.put( "url", url.toString() ); return new Message( mtype_, params ); } /** * Indicates whether this resource type is suitable for use * with a given MIME type. Note that the submitted content type * may contain additional parameters and have embedded whitespace etc * as permitted by RFC 2045. * * @param ctype content-type header value * @return true iff this resource type is suitable for use with * the given content type */ public boolean isContentType( String ctype ) { ctype = ctype.replaceAll( " *;.*", "" ) .replaceAll( "\\s+", "" ) .toLowerCase(); for ( int ic = 0; ic < ctypes_.length; ic++ ) { if ( ctype.startsWith( ctypes_[ ic ] ) ) { return true; } } return false; } /** * Indicates whether this resource type is suitable for use * with a resource having a given magic number. * * @param magic buffer containing the first few bytes of * resource content * @return true iff this resource type is suitable for use * with the given content */ public abstract boolean isMagic( byte[] magic ); /** * Returns the name of this resource type. * * @return name */ public String getName() { return name_; } public String toString() { return name_; } /** * Returns the known resource types. * * @return known instances of this class */ public static ResourceType[] getKnownResourceTypes() { return (ResourceType[]) RESOURCE_TYPES.clone(); } /** * Attempts to determine the resource type of a given URL by * making an HTTP HEAD request and looking at the Content-Type. * * @param url resource location * @return good guess at resource type, or null if can't be determined */ public static ResourceType readHeadResourceType( URL url ) { try { URLConnection uconn = url.openConnection(); if ( uconn instanceof HttpURLConnection ) { logger_.info( "HEAD " + url ); HttpURLConnection hconn = (HttpURLConnection) uconn; hconn.setInstanceFollowRedirects( true ); hconn.setRequestMethod( "HEAD" ); hconn.connect(); int code = hconn.getResponseCode(); if ( code == 200 ) { String ctype = hconn.getContentType(); logger_.info( "HEAD Content-Type: " + ctype ); return getMimeResourceType( ctype ); } else { logger_.warning( "HEAD response code " + code ); return null; } } else { return null; } } catch ( IOException e ) { logger_.log( Level.WARNING, "HEAD failed", e ); return null; } } /** * Attempts to determine the resource type of a given URL by * downloading the first part of its content and examining the * magic number. * * @param url resource location * @return good guess at resource type, or null if it can't be determined */ public static ResourceType readContentResourceType( URL url ) { // Acquire the magic number. byte[] buf = new byte[ MAGIC_SIZE ]; InputStream in = null; logger_.info( "GET " + url ); try { // Open a GET connection. URLConnection uconn = url.openConnection(); if ( uconn instanceof HttpURLConnection ) { HttpURLConnection hconn = (HttpURLConnection) uconn; hconn.setInstanceFollowRedirects( true ); hconn.connect(); int code = hconn.getResponseCode(); if ( code != 200 ) { logger_.warning( "GET response code " + code ); return null; } // The content-type may be usable here, even if the // presumed earlier call to HEAD failed // (for instance HEAD not implemented). String ctype = hconn.getContentType(); ResourceType rtype = getMimeResourceType( ctype ); if ( rtype != null ) { logger_.info( "GET Content-Type: " + ctype ); return rtype; } } else { uconn.connect(); } // Read the first few bytes into a buffer, then close the stream. in = uconn.getInputStream(); for ( int off = 0; off < MAGIC_SIZE; ) { int nr = in.read( buf, off, MAGIC_SIZE - off ); if ( nr > 0 ) { off += nr; } else { break; } } } catch ( IOException e ) { logger_.log( Level.WARNING, "GET failed", e ); } finally { if ( in != null ) { try { in.close(); } catch ( IOException e ) { } } } // Try to determine type from magic number. return getMagicResourceType( buf ); } /** * Try to identify a resource type from its MIME type. * * @param contentType content-type header * @return resource type, or null if not known */ private static ResourceType getMimeResourceType( String contentType ) { if ( contentType != null ) { for ( int i = 0; i < RESOURCE_TYPES.length; i++ ) { ResourceType rtype = RESOURCE_TYPES[ i ]; if ( rtype.isContentType( contentType ) ) { return rtype; } } } return null; } /** * Try to identify a resource type from its magic number. * * @param magic buffer containing first few bytes of resource content * @return resource type, or null if not known */ private static ResourceType getMagicResourceType( byte[] magic ) { for ( int i = 0; i < RESOURCE_TYPES.length; i++ ) { ResourceType rtype = RESOURCE_TYPES[ i ]; if ( rtype.isMagic( magic ) ) { logger_.info( "GET magic number looks like " + rtype ); return rtype; } } return null; } /** * Returns a ResourceType instance suitable for the table.load.votable * SAMP MType. * * @return VOTable resource type */ private static ResourceType createVOTableResourceType() { return new ResourceType( "VOTable", "table.load.votable", new String[] { "application/x-votable+xml", "text/xml" } ) { public boolean isMagic( byte[] buf ) { // Shocking hack that should work for UTF-8 and UTF-16*. String txt; try { txt = new String( buf, "US-ASCII" ); } catch ( UnsupportedEncodingException e ) { return false; } return txt.contains( "= 9 && (char) buf[ 0 ] == 'S' && (char) buf[ 1 ] == 'I' && (char) buf[ 2 ] == 'M' && (char) buf[ 3 ] == 'P' && (char) buf[ 4 ] == 'L' && (char) buf[ 5 ] == 'E' && (char) buf[ 6 ] == ' ' && (char) buf[ 7 ] == ' ' && (char) buf[ 8 ] == '='; } }; } /** * Returns a ResourceType instance suitable for the table.load.cdf * SAMP Mtype. * * @return CDF table resource type */ private static ResourceType createCdfTableResourceType() { return new ResourceType( "CDF", "table.load.cdf", new String[ 0 ] ) { public boolean isMagic( byte[] buf ) { if ( buf.length >= 8 ) { int m1 = ( buf[ 0 ] & 0xff ) << 24 | ( buf[ 1 ] & 0xff ) << 16 | ( buf[ 2 ] & 0xff ) << 8 | ( buf[ 3 ] & 0xff ) << 0; int m2 = ( buf[ 4 ] & 0xff ) << 24 | ( buf[ 5 ] & 0xff ) << 16 | ( buf[ 6 ] & 0xff ) << 8 | ( buf[ 7 ] & 0xff ) << 0; // Version 2.6+ only. return ( m1 == 0xcdf30001 || m1 == 0xcdf26002 ) && ( m2 == 0x0000ffff || m2 == 0xcccc0001 ); } else { return false; } } }; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/util/SampLoad.java000066400000000000000000000517671356450004300245410ustar00rootroot00000000000000package org.astrogrid.samp.util; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ComboBoxModel; import javax.swing.DefaultComboBoxModel; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.Timer; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import org.astrogrid.samp.Client; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.ResultHandler; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.gui.AbstractCallActionManager; import org.astrogrid.samp.gui.SendActionManager; import org.astrogrid.samp.gui.UniformCallActionManager; import org.astrogrid.samp.gui.GuiHubConnector; import org.astrogrid.samp.httpd.UtilServer; /** * Dialog window for sending a fixed load-type message to a selected * client or clients. * This is intended for use as a small free-standing SAMP client that * for instance can be used as a browser helper application. * *

A main method is supplied for command-line use. * * @author Mark Taylor * @since 21 Aug 2019 */ public class SampLoad extends JDialog { private final GuiHubConnector connector_; private final URL url_; private final String mtype_; private final JComboBox targetSelector_; private final JLabel statusField_; private final Action sendAct_; private Map responseMap_; private static final URL ICON_URL = SampLoad.class.getResource( "/org/astrogrid/samp/images/sampload.png" ); private static final Logger logger_ = Logger.getLogger( SampLoad.class.getName() ); /** * Constructor. * * @param connector connector * @param rtype resource type * @param URL resource URL * @param sendType type of item being sent (for user info) * @param location location of item being sent (for user info) */ public SampLoad( GuiHubConnector connector, final ResourceType rtype, final URL url, String location ) { super( (JDialog) null, "SAMP Loader", true ); connector_ = connector; url_ = url; mtype_ = rtype == null ? null : rtype.getMType(); // Prepare to send the message to selected target client(s). String tname = rtype == null ? null : rtype.getName(); AbstractCallActionManager callManager = new UniformCallActionManager( this, connector, mtype_, tname ) { protected Map createMessage() { return rtype.createMessage( url ); } protected ResultHandler createResultHandler( HubConnection conn, Message msg, Client[] recipients ) { final Collection recips = new HashSet( Arrays.asList( recipients ) ); return new ResultHandler() { // Record which responses we have got. public void result( Client responder, Response response ) { responseMap_.put( responder, response ); if ( response.isOK() ) { recips.remove( responder ); } updateStatus(); } // If all successful, just dismiss the window after // a short delay. If there were any problems, // leave it posted so that any error messages are visible. public void done() { boolean success = recips.size() == 0; if ( success ) { Timer timer = new Timer( 1000, new ActionListener() { public void actionPerformed( ActionEvent evt ) { closeDialog(); } } ); timer.setRepeats( false ); timer.start(); } } }; } }; // Provide a target client selection widget and associated send button. final ComboBoxModel targetModel; if ( rtype == null ) { targetModel = new DefaultComboBoxModel( new String[] { SendActionManager .BROADCAST_TARGET } ); sendAct_ = new AbstractAction( "Send to selected target" ) { public void actionPerformed( ActionEvent evt ) { } }; } else { targetModel = callManager.createTargetSelector(); sendAct_ = callManager.createTargetAction( targetModel ); } targetSelector_ = new JComboBox( targetModel ) { public Dimension getMaximumSize() { return super.getPreferredSize(); } }; // Provide a button that dismisses the window. Action cancelAct = new AbstractAction( "Cancel" ) { public void actionPerformed( ActionEvent evt ) { closeDialog(); } }; setDefaultCloseOperation( DISPOSE_ON_CLOSE ); // Update GUI on relevant SAMP events. connector_.addConnectionListener( new ChangeListener() { public void stateChanged( ChangeEvent evt ) { updateStatus(); } } ); targetModel.addListDataListener( new ListDataListener() { public void contentsChanged( ListDataEvent evt ) { updateSelector(); } public void intervalAdded( ListDataEvent evt ) { updateSelector(); } public void intervalRemoved( ListDataEvent evt ) { updateSelector(); } } ); // We don't have hub autodetect switched on, since in most cases // this will not be a long-lived application, but if the user // interacts with the window, take it as an opportunity // to see if a hub has started. getContentPane().addMouseListener( new MouseAdapter() { public void mouseEntered( MouseEvent evt ) { checkConnection(); } public void mouseExited( MouseEvent evt ) { checkConnection(); } public void mousePressed( MouseEvent evt ) { checkConnection(); } private void checkConnection() { try { connector_.getConnection(); } catch ( SampException e ) { } } } ); // Note when a send has started by resetting the response map to null. JButton sendButton = new JButton( sendAct_ ); sendButton.addActionListener( new ActionListener() { public void actionPerformed( ActionEvent evt ) { responseMap_ = new LinkedHashMap(); updateStatus(); } } ); // Place components. statusField_ = new JLabel(); JComponent displayPanel = new JPanel( new GridBagLayout() ); GridBagConstraints gbc = new GridBagConstraints(); addLine( displayPanel, gbc, "Location", createField( location ), true ); addLine( displayPanel, gbc, "MType", createField( mtype_ ), true ); addLine( displayPanel, gbc, "Target", targetSelector_, false ); addLine( displayPanel, gbc, "Status", statusField_, true ); JComponent buttLine = Box.createHorizontalBox(); buttLine.add( Box.createHorizontalGlue() ); buttLine.add( sendButton ); buttLine.add( Box.createHorizontalStrut( 10 ) ); buttLine.add( new JButton( cancelAct ) ); buttLine.add( Box.createHorizontalGlue() ); buttLine.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); Box qbox = Box.createVerticalBox(); Icon qicon = ICON_URL != null ? new ImageIcon( ICON_URL ) : UIManager.getIcon( "OptionPane.questionIcon" ); qbox.add( new JLabel( qicon ) ); qbox.add( Box.createVerticalGlue() ); qbox.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); Component hstrut = Box.createHorizontalStrut( 450 ); JComponent contentPane = (JComponent) getContentPane(); contentPane.setLayout( new BorderLayout() ); contentPane.add( displayPanel, BorderLayout.CENTER ); contentPane.add( buttLine, BorderLayout.SOUTH ); contentPane.add( qbox, BorderLayout.WEST ); contentPane.add( hstrut, BorderLayout.NORTH ); contentPane.setBorder( BorderFactory.createEmptyBorder( 5, 5, 5, 5 ) ); // Initialise state. updateSelector(); } /** * Updates the GUI according to current state. */ private void updateStatus() { final String status; final boolean canSend; if ( url_ == null ) { status = "No such file"; canSend = false; } else if ( mtype_ == null ) { status = "Unknown resource type (not " + commaJoin( ResourceType.getKnownResourceTypes() ) + ")"; canSend = false; } else if ( ! connector_.isConnected() ) { status = "No hub"; canSend = false; } else if ( targetSelector_.getItemCount() <= 1 ) { status = "No clients for " + mtype_; canSend = false; } else if ( responseMap_ == null ) { status = "Ready"; canSend = true; } else if ( responseMap_.size() == 0 ) { status = "sending ..."; canSend = false; } else { StringBuffer sbuf = new StringBuffer(); for ( Iterator it = responseMap_.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); if ( sbuf.length() > 0 ) { sbuf.append( "; " ); } Client client = (Client) entry.getKey(); Response response = (Response) entry.getValue(); sbuf.append( client + ": " ); if ( response.isOK() ) { sbuf.append( "OK" ); } else { ErrInfo err = response.getErrInfo(); if ( err == null ) { sbuf.append( "ERROR" ); } else { sbuf.append( response.getStatus() ) .append( " " ) .append( err.getErrortxt() ); } } } status = sbuf.toString(); canSend = false; } statusField_.setText( status ); sendAct_.setEnabled( canSend ); } /** * Updates the target client selector according to the current SAMP * client list. */ private void updateSelector() { // Note, the first item in the list corresponds to Broadcast // (all clients), and subsequent items are single clients. if ( targetSelector_.getItemCount() == 2 ) { targetSelector_.setSelectedIndex( 1 ); } else if ( targetSelector_.getSelectedIndex() < 0 ) { targetSelector_.setSelectedIndex( 0 ); } updateStatus(); } /** * Closes and disposes this dialogue. */ private void closeDialog() { setVisible( false ); dispose(); } /** * Utility method to append a labelled component to a JComponent * using GridBagLayout. * * @param panel container * @param gbc constraints object * @param labelTxt text of label * @param comp component to add * @param hfill true to fill horizontal line */ private static void addLine( JComponent panel, GridBagConstraints gbc, String labelTxt, JComponent comp, boolean hfill ) { GridBagLayout layer = (GridBagLayout) panel.getLayout(); gbc.gridy++; gbc.gridx = 0; gbc.anchor = GridBagConstraints.WEST; gbc.weightx = 0.0; gbc.insets = new Insets( 2, 5, 5, 2 ); JLabel label = new JLabel( labelTxt + ": " ); layer.setConstraints( label, gbc ); panel.add( label ); GridBagConstraints gbc1 = (GridBagConstraints) gbc.clone(); gbc1.gridx = 1; gbc1.anchor = GridBagConstraints.WEST; gbc1.weightx = 1.0; gbc1.fill = hfill ? GridBagConstraints.HORIZONTAL : GridBagConstraints.NONE; gbc1.gridwidth = GridBagConstraints.REMAINDER; layer.setConstraints( comp, gbc1 ); panel.add( comp ); } /** * Utility method to create an uneditable field with given text. * * @param txt text content * @return field component */ private static JTextField createField( String txt ) { JTextField field = new JTextField(); field.setEditable( false ); field.setText( txt ); field.setCaretPosition( 0 ); return field; } /** * Returns a comma-separated string joining the toString values of * the elements of a supplied array. * * @param items array of items * @return "i1, i2, ..., iN" */ private static String commaJoin( Object[] items ) { StringBuffer sbuf = new StringBuffer(); for ( int i = 0; i < items.length; i++ ) { if ( sbuf.length() > 0 ) { sbuf.append( ", " ); } sbuf.append( String.valueOf( items[ i ] ) ); } return sbuf.toString(); } /** * Tries to turn a string into a URL. * * @param location URL or filename * @return URL, or null */ private static final URL getUrl( String location ) { File f = new File( location ); if ( f.exists() ) { return SampUtils.fileToUrl( f ); } try { return new URL( location ); } catch ( MalformedURLException e ) { } return null; } /** * Main invocation method. * Use -help for help. * * @param args arg vector */ public static int runMain( String[] args ) throws IOException { ResourceType[] resourceTypes = ResourceType.getKnownResourceTypes(); // Assemble usage string. StringBuffer rtbuf = new StringBuffer(); for ( int ir = 0; ir < resourceTypes.length; ir++ ) { if ( rtbuf.length() > 0 ) { rtbuf.append( "|" ); } rtbuf.append( resourceTypes[ ir ].getName().toLowerCase() ); } String usage = new StringBuffer() .append( "\n Usage:" ) .append( "\n " + SampLoad.class.getName() ) .append( "\n " ) .append( " [-help]" ) .append( " [-/+verbose]" ) .append( "\n " ) .append( " [-rtype " + rtbuf + "]" ) .append( " " ) .append( "\n" ) .toString(); // Set up variables which can be set or changed by the argument list. ResourceType rtype = null; String location = null; int verbAdjust = 0; // Parse the argument list. List argList = new ArrayList( Arrays.asList( args ) ); for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.equals( "-rtype" ) && rtype == null && it.hasNext() ) { it.remove(); String rtName = (String) it.next(); it.remove(); for ( int ir = 0; ir < resourceTypes.length; ir++ ) { ResourceType rt = resourceTypes[ ir ]; if ( rtName.equalsIgnoreCase( rt.getName() ) ) { rtype = rt; } } if ( rtype == null ) { System.err.println( usage ); return 1; } } else if ( arg.startsWith( "-verb" ) ) { it.remove(); verbAdjust--; } else if ( arg.startsWith( "+verb" ) ) { it.remove(); verbAdjust++; } else if ( arg.equals( "-help" ) ) { it.remove(); System.out.println( usage ); return 0; } else if ( arg.startsWith( "-" ) || arg.startsWith( "+" ) ) { System.err.println( usage ); return 1; } else if ( location == null ) { it.remove(); location = arg; } else { System.err.println( usage ); return 1; } } if ( location == null ) { System.err.println( usage ); return 1; } // Set logging levels in accordance with flags. int logLevel = Level.WARNING.intValue() + 100 * verbAdjust; Logger.getLogger( "org.astrogrid.samp" ) .setLevel( Level.parse( Integer.toString( logLevel ) ) ); // Interpret location. final URL url; if ( location == null ) { System.err.println( usage ); return 1; } else { url = getUrl( location ); } if ( url == null ) { logger_.warning( "Bad location: " + location ); } // Make sure we have a resource type. This may have been supplied // on the command line, but if not, try to identify it by retrieving // the metadata or content of the resource itself. if ( url != null ) { if ( rtype == null ) { rtype = ResourceType.readHeadResourceType( url ); } if ( rtype == null ) { rtype = ResourceType.readContentResourceType( url ); } if ( rtype == null ) { logger_.warning( "Unknown resource type: " + url ); } } // Prepare client metadata. Metadata meta = new Metadata(); meta.setName( "SampLoad" ); try { meta.setIconUrl( UtilServer.getInstance() .exportResource( ICON_URL ).toString() ); } catch ( Exception e ) { logger_.log( Level.WARNING, "Can't export application icon", e ); } meta.setDescriptionText( new StringBuffer() .append( "Browser helper application to forward downloaded files" ) .append( " (" + rtype + ") " ) .append( "to SAMP Hub" ) .toString() ); meta.put( "author.name", "Mark Taylor" ); // Prepare hub registration. ClientProfile profile = DefaultClientProfile.getProfile(); GuiHubConnector connector = new GuiHubConnector( profile ); connector.declareMetadata( meta ); connector.declareSubscriptions( connector.computeSubscriptions() ); connector.setActive( rtype != null ); // Send message under GUI control. SampLoad dialog = new SampLoad( connector, rtype, url, location ); dialog.pack(); dialog.setLocationRelativeTo( null ); dialog.setVisible( true ); connector.setActive( false ); return 0; } /** * Main method. */ public static void main( String[] args ) throws IOException { int status = runMain( args ); System.exit( status ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/000077500000000000000000000000001356450004300217565ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/AuthResourceBundle.java000066400000000000000000000253431356450004300263730ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.logging.Logger; /** * ResourceBundle for internationalising the Web Profile authorization * dialogue. * * @author Mark Taylor * @since 15 Jul 2011 */ public class AuthResourceBundle extends ResourceBundle { private final Hashtable map_; private static final Logger logger_ = Logger.getLogger( AuthResourceBundle.class.getName() ); /** * Constructs default (English) instance. */ public AuthResourceBundle() { this( getDefaultContent() ); } /** * Constructs a bundle based on a Content implementation. * * @param content contains information required for bundle */ protected AuthResourceBundle( Content content ) { map_ = new Hashtable(); Method[] methods = getContentMethods(); Object[] noArgs = new Object[ 0 ]; for ( int im = 0; im < methods.length; im++ ) { Method method = methods[ im ]; String mname = method.getName(); try { map_.put( method.getName(), method.invoke( content, noArgs ) ); } catch ( IllegalAccessException e ) { throw (RuntimeException) new RuntimeException( "Failed to call method " + method.getName() ) .initCause( e ); } catch ( InvocationTargetException e ) { throw (RuntimeException) new RuntimeException( "Failed to call method " + method.getName() ) .initCause( e ); } } checkHasAllKeys( this ); } protected final Object handleGetObject( String key ) { return map_.get( key ); } public final Enumeration getKeys() { return map_.keys(); } /** * Returns a Content object based on a bundle which has the keys * that AuthResourceBundle is supposed to have. * If any of the required keys are missing, the result falls back * to a default bundle. * * @param bundle resource bundle * @return content object guaranteed to have non-null contents for * all its attributes */ public static Content getAuthContent( final ResourceBundle bundle ) { try { checkHasAllKeys( bundle ); } catch ( MissingResourceException e ) { logger_.warning( "Some keys missing from localised auth resource " + "bundle; using English" ); return getDefaultContent(); } InvocationHandler ihandler = new InvocationHandler() { public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { String key = method.getName(); Class rclazz = method.getReturnType(); if ( String.class.equals( rclazz ) ) { return bundle.getString( key ); } else { throw new RuntimeException( "Unsuitable return type " + rclazz.getName() + " (shouldn't happen)" ); } } }; return (Content) Proxy .newProxyInstance( AuthResourceBundle.class.getClassLoader(), new Class[] { Content.class }, ihandler ); } /** * Returns all the methods of the Content interface which correspond * to AuthResourceBundle entries. * * @return resource bundle methods, all have no arguments and return String */ static Method[] getContentMethods() { return Content.class.getMethods(); } /** * Determines if a bundle has all the required keys for this class. * * @param bundle bundle to test * @return true iff bundle has all required keys */ static void checkHasAllKeys( ResourceBundle bundle ) { Collection bkeys = new HashSet(); for ( Enumeration en = bundle.getKeys(); en.hasMoreElements(); ) { bkeys.add( en.nextElement() ); } Collection mnames = new HashSet(); Method[] methods = getContentMethods(); for ( int im = 0; im < methods.length; im++ ) { mnames.add( methods[ im ].getName() ); } mnames.removeAll( bkeys ); if ( ! mnames.isEmpty() ) { throw new MissingResourceException( "Missing resources " + mnames, AuthResourceBundle.class.getName(), mnames.iterator().next().toString() ); } } /** * Returns a default Content implementation. * * @return english content */ private static Content getDefaultContent() { return new AuthResourceBundle_en.EnglishContent(); } /** * Returns a string suitable for entry into a .properties file * for a given Method of a given Content object. * * @param content auth resource content * @param method Content method (public String x()) */ private static String toPropertyString( Content content, Method method ) { try { String value = (String) method.invoke( content, new Object[ 0 ] ); value = value.replaceAll( "\n", "\\\\n" ); StringBuffer sbuf = new StringBuffer(); sbuf.append( method.getName() ) .append( '=' ); for ( int i = 0; i < value.length(); i++ ) { char c = value.charAt( i ); if ( c == '\n' ) { sbuf.append( "\\n" ); } else if ( c >= 32 && c < 128 ) { sbuf.append( c ); } else { String xs = Integer.toHexString( (int) c ); sbuf.append( "\\u" ); for ( int j = xs.length(); j < 4; j++ ) { sbuf.append( '0' ); } sbuf.append( xs ); } } return sbuf.toString(); } catch ( IllegalAccessException e ) { throw (RuntimeException) new RuntimeException( "Failed to call method " + method.getName() ) .initCause( e ); } catch ( InvocationTargetException e ) { throw (RuntimeException) new RuntimeException( "Failed to call method " + method.getName() ) .initCause( e ); } } /** * Writes a template .properties file. Sensitive to the locale. */ public static void main( String[] args ) { ResourceBundle lBundle = ResourceBundle.getBundle( AuthResourceBundle.class.getName() ); Content lContent = getAuthContent( lBundle ); Content dContent = getDefaultContent(); Method[] methods = getContentMethods(); System.out.println( "# Template for " + AuthResourceBundle.class.getName() + "_xx.properties file," ); System.out.println( "# giving localised text " + "for Web Profile client authorization dialogue." ); System.out.println( "# Please fill in language-specific values for " + "each key, as in the example." ); System.out.println( "# Follow the capitalization and punctuation " + "of the English version." ); System.out.println( "# Long lines should be broken up with return " + "characters (\\n)." ); System.out.println( "# Encoding is ISO 8859-1; " + "see java.util.Properties docs for detailed " + "syntax." ); System.out.println( "#" ); System.out.println( "# Alternatively, implement " + AuthResourceBundle.class.getName() + "_xx" ); System.out.println( "# using " + AuthResourceBundle_en.class.getName() + " as an example." ); System.out.println(); for ( int im = 0; im < methods.length; im++ ) { Method method = methods[ im ]; System.out.println( "# " + toPropertyString( dContent, method ) ); System.out.println( toPropertyString( lContent, method ) ); } } /** * Defines the keys and value types required for a bundle of this class. * See the English language implementation in * {@link AuthResourceBundle_en} for example text. * *

All methods have no arguments and return a String. * The methods with names * that end "Lines" should return text which contains * line breaks (\n characters). Each such line will * be displayed as it stands in the GUI, so it shouldn't be too long. * *

The method names define the keys which can be used if a * property resource file is used to supply the content. */ public static interface Content { /** * Returns the title for the confirmation window. */ String windowTitle(); /** * Returns lines introducing the registration request. */ String appIntroductionLines(); /** * Returns the word meaning "Name" (initial capitalised). */ String nameWord(); /** * Returns the word meaning "Origin" (initial capitalised). */ String originWord(); /** * Returns the word meaning "undeclared" (not capitalised). */ String undeclaredWord(); /** * Returns lines suitable explaining the privileges that a * registered client will have. */ String privilegeWarningLines(); /** * Returns lines with advice on whether you should accept or decline. */ String adviceLines(); /** * Returns a line asking whether to authorize (yes/no). */ String questionLine(); /** * Returns the word meaning "Yes" (initial capitalised). */ String yesWord(); /** * Returns the word meaning "No" (initial capitalised). */ String noWord(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/AuthResourceBundle_de.java000066400000000000000000000033401356450004300270340ustar00rootroot00000000000000package org.astrogrid.samp.web; /** * AuthResourceBundle with German text. * * @author Markus Demleitner * @author Mark Taylor * @since 1 Aug 2011 */ public class AuthResourceBundle_de extends AuthResourceBundle { /** * Constructor. */ public AuthResourceBundle_de() { super( new GermanContent() ); } /** * Content implementation for English. */ private static class GermanContent implements Content { public String windowTitle() { return "SAMP Zugriffskontrolle"; } public String appIntroductionLines() { return "Folgendes Programm (vermutlich im Browser laufend)\n" + "m\u00f6chte sich am SAMP Hub anmelden:"; } public String nameWord() { return "Name"; } public String originWord() { return "Auf Seite"; } public String undeclaredWord() { return "Nicht gegeben"; } public String privilegeWarningLines() { return "Wenn Sie dies zulassen, k\u00f6nnte der Dienst unter\n" + "Umst\u00e4nden auf Dateien oder andere Resourcen auf\n" + "Ihrem Rechner zugreifen k\u00f6nnen."; } public String adviceLines() { return "Lassen Sie die Verbindung nur zu, wenn Sie gerade\n" + "auf einer Seite, der Sie vertrauen, eine Handlung\n" + "ausgef\u00fchrt haben, die SAMP anspricht."; } public String questionLine() { return "Die Verbindung erlauben?"; } public String yesWord() { return "Ja"; } public String noWord() { return "Nein"; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/AuthResourceBundle_en.java000066400000000000000000000031601356450004300270460ustar00rootroot00000000000000package org.astrogrid.samp.web; /** * AuthResourceBundle with English text. * * @author Mark Taylor * @since 15 Jul 2011 */ public class AuthResourceBundle_en extends AuthResourceBundle { /** * Constructor. */ public AuthResourceBundle_en() { super( new EnglishContent() ); } /** * Content implementation for English. */ static class EnglishContent implements Content { public String windowTitle() { return "SAMP Hub Security"; } public String appIntroductionLines() { return "The following application, probably running in a browser,\n" + "is requesting SAMP Hub registration:"; } public String nameWord() { return "Name"; } public String originWord() { return "Origin"; } public String undeclaredWord() { return "undeclared"; } public String privilegeWarningLines() { return "If you permit this, it may be able to access local files\n" + "and other resources on your computer."; } public String adviceLines() { return "You should only accept if you have just performed\n" + "some action in the browser, on a web site you trust,\n" + "that you expect to have caused this."; } public String questionLine() { return "Do you authorize connection?"; } public String yesWord() { return "Yes"; } public String noWord() { return "No"; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/AuthResourceBundle_fr.java000066400000000000000000000035151356450004300270570ustar00rootroot00000000000000package org.astrogrid.samp.web; /** * AuthResourceBundle with French text. * * @author Thomas Boch * @author Mark Taylor * @since 23 Aug 2011 */ public class AuthResourceBundle_fr extends AuthResourceBundle { /** * Constructor. */ public AuthResourceBundle_fr() { super( new FrenchContent() ); } /** * Content implementation for French. */ private static class FrenchContent implements Content { public String windowTitle() { return "Avertissement de s\u00e9curit\u00e9 du hub SAMP"; } public String appIntroductionLines() { return "L'application suivante, qui s'ex\u00e9cute probablement " + "depuis un\n" + "navigateur, demande \u00e0 s'enregistrer " + "aupr\u00e8s du hub SAMP:"; } public String nameWord() { return "Nom"; } public String originWord() { return "Origine"; } public String undeclaredWord() { return "Inconnue"; } public String privilegeWarningLines() { return "Si vous l'autorisez, elle pourra acc\u00e9der aux " + "fichiers locaux\n" + "et autres ressources de votre ordinateur."; } public String adviceLines() { return "Acceptez uniquement si vous venez d'effectuer dans le " + "navigateur\n" + "une action, sur un site de confiance, susceptible d'avoir " + "entra\u00een\u00e9\n" + "cette demande."; } public String questionLine() { return "Acceptez-vous?"; } public String yesWord() { return "Oui"; } public String noWord() { return "Non"; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/AuthResourceBundle_it.java000066400000000000000000000034101356450004300270560ustar00rootroot00000000000000package org.astrogrid.samp.web; /** * AuthResourceBundle with English text. * * @author Luigi Paioro * @author Mark Taylor * @since 15 Jul 2011 */ public class AuthResourceBundle_it extends AuthResourceBundle { /** * Constructor. */ public AuthResourceBundle_it() { super( new ItalianContent() ); } /** * Content implementation for Italian. */ private static class ItalianContent implements Content { public String windowTitle() { return "Sicurezza del SAMP Hub"; } public String appIntroductionLines() { return "Il seguente programma, probabilmente eseguito all'interno\n" + "di un browser, chiede di essere registrato al SAMP Hub:"; } public String nameWord() { return "Nome"; } public String originWord() { return "Origine"; } public String undeclaredWord() { return "non dichiarato"; } public String privilegeWarningLines() { return "Se ne consentite la registrazione, esso potrebbe accedere\n" + "ai files locali e ad altre risorse del vostro computer."; } public String adviceLines() { return "Il vostro consenso dovrebbe essere dato solo se avete\n" + "appena eseguito qualche azione con il browser,\n" + "su un sito Web conosciuto, che vi aspettate possa aver\n" + "causato questa richiesta."; } public String questionLine() { return "Autorizzate la registrazione?"; } public String yesWord() { return "S\u00ec"; } public String noWord() { return "No"; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/Callback.java000066400000000000000000000051501356450004300243160ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.List; import java.util.Map; import org.astrogrid.samp.SampMap; import org.astrogrid.samp.SampUtils; /** * Map representing a client callback from the hub. * It normally contains a callback method name and a list of parameters. * An instance of this class can be used to correspond to one of the calls * in the {@link org.astrogrid.samp.client.CallableClient} interface. * * @author Mark Taylor * @since 2 Feb 2011 */ class Callback extends SampMap { /** Key for the callback method name (a string). */ public static final String METHODNAME_KEY = "samp.methodName"; /** Key for the callback parameters (a list). */ public static final String PARAMS_KEY = "samp.params"; private static final String[] KNOWN_KEYS = new String[] { METHODNAME_KEY, PARAMS_KEY, }; /** * Constructs an empty callback. */ public Callback() { super( KNOWN_KEYS ); } /** * Constructs a callback based on an existing map. * * @param map contents */ public Callback( Map map ) { this(); putAll( map ); } /** * Constructs a callback given a method name and parameter list. */ public Callback( String methodName, List params ) { this(); setMethodName( methodName ); setParams( params ); } /** * Sets the method name. * * @param methodName method name */ public void setMethodName( String methodName ) { put( METHODNAME_KEY, methodName ); } /** * Returns the method name. * * @return method name */ public String getMethodName() { return getString( METHODNAME_KEY ); } /** * Sets the parameter list. * * @param params parameter list */ public void setParams( List params ) { SampUtils.checkList( params ); put( PARAMS_KEY, params ); } /** * Returns the parameter list. * * @return parameter list */ public List getParams() { return getList( PARAMS_KEY ); } public void check() { super.check(); checkHasKeys( new String[] { METHODNAME_KEY, PARAMS_KEY, } ); SampUtils.checkString( getMethodName() ); SampUtils.checkList( getParams() ); } /** * Returns a given map as a Callback object. * * @param map map * @return callback */ public static Callback asCallback( Map map ) { return ( map instanceof Callback || map == null ) ? (Callback) map : new Callback( map ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ClientAuthorizer.java000066400000000000000000000021421356450004300261130ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.Map; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.HttpServer; /** * Defines authorization functionality which is used to determine whether * a client is permitted to register with the hub. * * @author Mark Taylor * @since 2 Feb 2011 */ public interface ClientAuthorizer { /** * Indicates whether an HTTP request representing an otherwise * unauthorized connection attempt will be permitted access to * sensitive system resources. If so, the method exits normally. * If authorization is denied, a SampException is thrown, * with a message that indicates the reason for denial. * * @param request incoming HTTP request * @param securityMap credential items supplied explicitly by * aspiring client to support its registration * request * @throws SampException with reason if authorization is denied */ void authorize( HttpServer.Request request, Map securityMap ) throws SampException; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ClientAuthorizers.java000066400000000000000000000124741356450004300263070ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.HttpServer; /** * Utility class containing ClientAuthorizer implementations. * * @author Mark Taylor * @since 2 Feb 2011 */ public class ClientAuthorizers { /** * Authorizer which always denies access, * with INFO logging either way. */ public static final ClientAuthorizer FALSE = createLoggingClientAuthorizer( createFixedClientAuthorizer( false ), Level.INFO, Level.INFO ); /** * Authorizer which always permits access, * with WARNING logging either way. */ public static final ClientAuthorizer TRUE = createLoggingClientAuthorizer( createFixedClientAuthorizer( true ), Level.WARNING, Level.WARNING ); /** * Authorizer which queries the user via a popup dialogue, * with INFO logging either way. */ private static ClientAuthorizer swingAuth_; private static final Logger logger_ = Logger.getLogger( ClientAuthorizers.class.getName() ); private ClientAuthorizers() { } /** * Returns a new authorizer instance which always produces the same * authorization status. * * @param policy true for accept, false for deny * @return new authorizer */ public static ClientAuthorizer createFixedClientAuthorizer( final boolean policy ) { if ( policy ) { return new ClientAuthorizer() { public void authorize( HttpServer.Request request, Map securityMap ) { } }; } else { return new ClientAuthorizer() { public void authorize( HttpServer.Request request, Map securityMap ) throws SampException { throw new SampException( "All registration requests " + "unconditionally denied" ); } }; } } /** * Returns a new authorizer instance based on an existing one which * logs authorization results through the logging system. * * @param auth base authorizer * @param acceptLevel logging level at which auth acceptances are logged * @param refuseLevel logging level at which auth refusals are logged * @return new authorizer */ public static ClientAuthorizer createLoggingClientAuthorizer( final ClientAuthorizer auth, final Level acceptLevel, final Level refuseLevel ) { return new ClientAuthorizer() { public synchronized void authorize( HttpServer.Request request, Map securityMap ) throws SampException { boolean isAuth; try { auth.authorize( request, securityMap ); log( true, securityMap, null ); } catch ( SampException e ) { log( false, securityMap, e ); throw e; } } private void log( boolean accept, Map securityMap, SampException err ) { String clTxt = securityMap.containsKey( Metadata.NAME_KEY ) ? securityMap.get( Metadata.NAME_KEY ).toString() : securityMap.toString(); if ( accept ) { logger_.log( acceptLevel, "Accepted registration for client " + clTxt ); } else { logger_.log( refuseLevel, "Rejected registration for client " + clTxt + "(" + err.getMessage() + ")" ); } } }; } /** * Returns the mandatory application name entry from the security map * supplied explicitly by clients wishing to register. * The relevant key is "samp.name" (Metadata.NAME_KEY). * If it's not present and correct, a SampException is thrown * indicating that registration is rejected. * * @param securityMap map supplied by client * @return value of samp.name key, not null * @throws SampException if name not present */ public static String getAppName( Map securityMap ) throws SampException { String nameKey = Metadata.NAME_KEY; assert "samp.name".equals( nameKey ); Object appNameObj = securityMap.get( nameKey ); final String appName; if ( appNameObj instanceof String ) { return (String) appNameObj; } else if ( appNameObj == null ) { throw new SampException( "No " + nameKey + " entry in securityInfo map" ); } else { throw new SampException( "Wrong data type (not string) for " + nameKey + " entry in securityInfo map" ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ClientCallbackOperation.java000066400000000000000000000130111356450004300273310ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.client.CallableClient; /** * Represents one of the possible callbacks which may be invoked on a * CallableClient. The {@link #invoke} static method arranges for a Callback * acquired from the hub to be dispatched to a CallableClient. * * @author Mark Taylor * @since 3 Feb 2011 */ abstract class ClientCallbackOperation { private final String fqName_; private final Class[] sampSig_; private static final Map OPERATION_MAP = createOperationMap(); /** * Constructor. * * @param unqualified callback method name * @param signature of callback; an array of SAMP-friendly classes, * one for each argument */ private ClientCallbackOperation( String methodName, Class[] sampSig ) { fqName_ = WebClientProfile.WEBSAMP_CLIENT_PREFIX + methodName; sampSig_ = sampSig; } /** * Makes a call to a callable client of the method represented by * this operation with a given list of parameters. * No checking is performed on the parameter list. * *

This method should be private really, but abstract private is * not permitted. * * @param client target callable client * @param paramList parameters for call, assumed to be valid */ abstract void dispatch( CallableClient client, List paramList ) throws Exception; /** * Dispatches a callback to a CallableClient. * * @param callback callback acquired from the hub * @param client client which should execute callback */ public static void invoke( Callback callback, CallableClient client ) throws Exception { callback.check(); String methodName = callback.getMethodName(); List paramList = callback.getParams(); ClientCallbackOperation op = (ClientCallbackOperation) OPERATION_MAP.get( methodName ); if ( op == null ) { throw new UnsupportedOperationException( "Unknown callback operation " + methodName ); } else { boolean sigOk = op.sampSig_.length == paramList.size(); for ( int i = 0; sigOk && i < op.sampSig_.length; i++ ) { sigOk = sigOk && op.sampSig_[ i ] .isAssignableFrom( paramList.get( i ).getClass() ); } if ( ! sigOk ) { throw new IllegalArgumentException( methodName + " callback signature mismatch" ); } else { op.dispatch( client, paramList ); } } } /** * Returns a map, keyed by unqualified operation name, * of known callback operations. * * @param String->ClientCallbackOperation map */ private static Map createOperationMap() { // First assemble an array of known callback operations. // It would be possible to assemble this array using reflection // on the CallableClient interface, but more trouble than it's // worth for three methods. ClientCallbackOperation[] operations = new ClientCallbackOperation[] { new ClientCallbackOperation( "receiveNotification", new Class[] { String.class, Map.class } ) { public void dispatch( CallableClient client, List params ) throws Exception { client.receiveNotification( (String) params.get( 0 ), new Message( (Map) params.get( 1 ) ) ); } }, new ClientCallbackOperation( "receiveCall", new Class[] { String.class, String.class, Map.class } ) { public void dispatch( CallableClient client, List params ) throws Exception { client.receiveCall( (String) params.get( 0 ), (String) params.get( 1 ), new Message( (Map) params.get( 2 ) ) ); } }, new ClientCallbackOperation( "receiveResponse", new Class[] { String.class, String.class, Map.class } ) { public void dispatch( CallableClient client, List params ) throws Exception { client.receiveResponse( (String) params.get( 0 ), (String) params.get( 1 ), new Response( (Map) params.get( 2 ) ) ); } }, }; // Turn it into a map keyed by operation name, and return. Map opMap = new HashMap(); for ( int i = 0; i < operations.length; i++ ) { ClientCallbackOperation op = operations[ i ]; opMap.put( op.fqName_, op ); } return Collections.unmodifiableMap( opMap ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/CorsHttpServer.java000066400000000000000000000311541356450004300255620ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.SocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import org.astrogrid.samp.httpd.HttpServer; /** * HttpServer which allows or rejects cross-origin access according to * the W3C Cross-Origin Resource Sharing standard. * This standard is used by XMLHttpResource Level 2 and some other * web-based platforms, implemented by a number of modern browsers, * and works by the browser inserting and interpreting special headers * when cross-origin requests are made by sandboxed clients. * The effect is that sandboxed clients will under some circumstances * be permitted to access resources served by instances of this server, * where they wouldn't for an HTTP server which did not take special * measures. * * @author Mark Taylor * @since 2 Feb 2011 * @see Cross-Origin Resource Sharing W3C Standard */ public class CorsHttpServer extends HttpServer { private final OriginAuthorizer authorizer_; private static final String ORIGIN_KEY = "Origin"; private static final String ALLOW_ORIGIN_KEY = "Access-Control-Allow-Origin"; private static final String REQUEST_METHOD_KEY = "Access-Control-Request-Method"; private static final String ALLOW_METHOD_KEY = "Access-Control-Allow-Methods"; private static final String ALLOW_HEADERS_KEY = "Access-Control-Allow-Headers"; // This regex is constructed with reference to RFC6454 and RFC3986. // It is less rigorous than those, since the host production in RFC3986 // is quite complex, but the required checking is not all that critical. private static final Pattern ORIGIN_REGEX = Pattern.compile( "[A-Za-z][A-Za-z0-9+.-]*://.+" ); private static final InetAddress localHostAddress_ = getLocalHostAddress(); private static final Logger logger_ = Logger.getLogger( CorsHttpServer.class.getName() ); /** * System property ({@value}) which can be used to supply host addresses * explicitly permitted to connect via the Web Profile alongside * the local host. * Normally any non-local host is blocked from access to the CORS * web server for security reasons. However, any host specified * by hostname or IP number as one element of a comma-separated * list in the value of this system property will also be allowed. * This might be used to allow access from a "friendly" near-local * host like a tablet. */ public static final String EXTRAHOSTS_PROP = "jsamp.web.extrahosts"; /** Set of permitted InetAddrs along side localhost. */ private static final Set extraAddrSet_ = new HashSet( Arrays.asList( getExtraHostAddresses() ) ); /** * Constructor. * * @param socket socket hosting the service * @param authorizer defines which domains requests will be * permitted from */ public CorsHttpServer( ServerSocket socket, OriginAuthorizer authorizer ) throws IOException { super( socket ); authorizer_ = authorizer; } public Response serve( Request request ) { if ( ! isPermittedHost( request.getRemoteAddress() ) ) { return createNonLocalErrorResponse( request ); } Map hdrMap = request.getHeaderMap(); String method = request.getMethod(); String originTxt = getHeader( hdrMap, ORIGIN_KEY ); if ( originTxt != null ) { String reqMethod = getHeader( hdrMap, REQUEST_METHOD_KEY ); if ( method.equals( "OPTIONS" ) && reqMethod != null ) { return servePreflightOriginRequest( request, originTxt, reqMethod ); } else { return serveSimpleOriginRequest( request, originTxt ); } } else { return super.serve( request ); } } /** * Does the work for serving simple requests which bear an * origin header. Simple requests are effectively ones which do not * require pre-flight requests - see the CORS standard for details. * * @param request HTTP request * @param originTxt content of the Origin header * @return HTTP response */ private Response serveSimpleOriginRequest( Request request, String originTxt ) { Response response = super.serve( request ); if ( isAuthorized( originTxt ) ) { Map headerMap = response.getHeaderMap(); if ( getHeader( headerMap, ALLOW_ORIGIN_KEY ) == null ) { headerMap.put( ALLOW_ORIGIN_KEY, originTxt ); } } return response; } /** * Does the work for serving pre-flight requests. * See the CORS standard for details. * * @param request HTTP request * @param originTxt content of the Origin header * @param reqMethod content of the Access-Control-Request-Method header * @return HTTP response */ private Response servePreflightOriginRequest( Request request, String originTxt, String reqMethod ) { Map hdrMap = new LinkedHashMap(); hdrMap.put( "Content-Length", "0" ); if ( isAuthorized( originTxt ) ) { hdrMap.put( ALLOW_ORIGIN_KEY, originTxt ); hdrMap.put( ALLOW_METHOD_KEY, reqMethod ); hdrMap.put( ALLOW_HEADERS_KEY, "Content-Type" ); // allow all here? } return new Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) { } }; } /** * Returns an HTTP error response complaining about attempted access * from a disallowed host. * * @param request offending request * @return HTTP 403 response */ public static Response createNonLocalErrorResponse( Request request ) { int status = 403; String msg = "Forbidden"; String method = request.getMethod(); if ( "HEAD".equals( method ) ) { return createErrorResponse( status, msg ); } else { Map hdrMap = new LinkedHashMap(); hdrMap.put( HDR_CONTENT_TYPE, "text/plain" ); byte[] mbuf; try { mbuf = ( "Access to server from non-local hosts " + "is not permitted.\r\n" ) .getBytes( "UTF-8" ); } catch ( UnsupportedEncodingException e ) { logger_.warning( "Unsupported UTF-8??" ); mbuf = new byte[ 0 ]; } final byte[] mbuf1 = mbuf; hdrMap.put( "Content-Length", Integer.toString( mbuf1.length ) ); return new Response( status, msg, hdrMap ) { public void writeBody( OutputStream out ) throws IOException { out.write( mbuf1 ); out.flush(); } }; } } /** * Determines whether a given origin is permitted access. * This is done by interrogating this server's OriginAuthorizer policy. * Results are cached. * * @param originTxt content of Origin header */ private boolean isAuthorized( String originTxt ) { // CORS sec 5.1 says multiple space-separated origins may be present // - but why?? Treat the string as a single origin for now. // Not incorrect, though possibly annoying if the same origin // crops up multiple times in different sets (unlikely as far // as I can see). boolean hasLegalOrigin; try { checkOriginList( originTxt ); hasLegalOrigin = true; } catch ( RuntimeException e ) { logger_.warning( "Origin header: " + e.getMessage() ); hasLegalOrigin = false; } return hasLegalOrigin && authorizer_.authorize( originTxt ); } /** * Indicates whether a network address is known to represent * a host permitted to access this server. * That generally means the local host, but "extra" hosts may be * permitted as well. * * @param address socket address * @return true iff address is known to be permitted */ public boolean isPermittedHost( SocketAddress address ) { return isLocalHost( address ) || isExtraHost( address ); } /** * Indicates whether the given socket address is from the local host. * * @param address socket to test * @return true if the socket is known to be local */ public static boolean isLocalHost( SocketAddress address ) { if ( address instanceof InetSocketAddress ) { InetAddress iAddress = ((InetSocketAddress) address).getAddress(); return iAddress != null && ( iAddress.isLoopbackAddress() || iAddress.equals( localHostAddress_ ) ); } else { return false; } } /** * Returns the inet address of the local host, or null if not available. * * @return local host address or null */ private static InetAddress getLocalHostAddress() { try { return InetAddress.getLocalHost(); } catch ( UnknownHostException e ) { logger_.log( Level.WARNING, "Can't determine local host address", e ); return null; } } /** * Acquires and returns a list of permitted non-local hosts from the * environment. * * @return list of addresses for non-local hosts permitted to access * CORS web servers in this JVM */ private static InetAddress[] getExtraHostAddresses() { String list; try { list = System.getProperty( EXTRAHOSTS_PROP ); } catch ( SecurityException e ) { list = null; } String[] names; if ( list != null ) { list = list.trim(); names = list.length() > 0 ? list.split( ", *" ) : new String[ 0 ]; } else { names = new String[ 0 ]; } int naddr = names.length; List addrList = new ArrayList(); for ( int i = 0; i < naddr; i++ ) { String name = names[ i ]; try { addrList.add( InetAddress.getByName( name ) ); logger_.warning( "Adding web hub exception for host " + "\"" + name + "\"" ); } catch ( UnknownHostException e ) { logger_.warning( "Unknown host \"" + name + "\"" + " - not adding web hub exception" ); } } return (InetAddress[]) addrList.toArray( new InetAddress[ 0 ] ); } /** * Indicates whether a given address represents one of the "extra" hosts * permitted to access this server alongside the localhost. * * @param addr address of non-local host to test * @return true iff host is permitted to access this server */ public static boolean isExtraHost( SocketAddress addr ) { return addr instanceof InetSocketAddress && extraAddrSet_.contains( ((InetSocketAddress) addr) .getAddress() ); } /** * Checks that the content of an Origin header is syntactically legal. * * @param originTxt content of Origin header * @throws IllegalArgumentExeption if originTxt does not represent * a legal origin or (non-empty) list of origins */ private static void checkOriginList( String originTxt ) { String[] origins = originTxt.split( " +" ); if ( origins.length > 0 ) { for ( int i = 0; i < origins.length; i++ ) { if ( ! ORIGIN_REGEX.matcher( origins[ i ] ).matches() ) { throw new IllegalArgumentException( "Bad origin syntax: \"" + origins[ i ] + "\"" ); } } } else { throw new IllegalArgumentException( "No origins supplied" ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/CredentialPresenter.java000066400000000000000000000045271356450004300265730ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.Map; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.HttpServer; /** * Extracts credentials for presentation to the user from available * information, so that the user can decide whether to allow registration. * * @author Mark Taylor * @since 18 Jun 2016 */ public interface CredentialPresenter { /** * Returns an object which contains user-directed credential messages, * given available information from the registration request. * If the request or securityMap can be determined to be * definitely unsuitable for registration, a SampException is thrown. * * @param request HTTP request associated with the registration request * @param securityMap information explicitly supplied by the aspiring * client in support of its application to register * @param authContent content of AuthResourceBundle bundle * @return items for presentation to the user * @throws SampException if credentials should definitely not be accepted */ Presentation createPresentation( HttpServer.Request request, Map securityMap, AuthResourceBundle.Content authContent ) throws SampException; /** * Aggregates credential information to be presented to the user. */ interface Presentation { /** * Returns an ordered map of String->String entries * containing name, value pairs. * * @return map with ordered entries */ Map getAuthEntries(); /** * Returns an array of "message" objects providing additional * information for the user. * *

If the supplied identity information looks OK, * then returning an empty array is a good idea. * But if there is some kind of inconsistency or cause for alarm, * a sequence of GUI elements may be returned. * *

The return value is suitable for use as the msg * argument of one of JOptionPane's methods. * * @return message array describing the applicant to the user * @see javax.swing.JOptionPane */ Object[] getAuthLines(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ExtremeSwingClientAuthorizer.java000066400000000000000000000072141356450004300304620ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; import java.net.URL; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.border.Border; import org.astrogrid.samp.Client; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.gui.IconStore; import org.astrogrid.samp.httpd.HttpServer; /** * Client authorizer implementation that does its very best to discourage * users from accepting regitrations. * * @author Mark Taylor * @since 29 Sep 2011 */ public class ExtremeSwingClientAuthorizer implements ClientAuthorizer { private final Component parent_; /** * Constructor. * * @param parent parent component, may be null */ public ExtremeSwingClientAuthorizer( Component parent ) { parent_ = parent; if ( GraphicsEnvironment.isHeadless() ) { throw new HeadlessException( "No graphics - lucky escape" ); } } public void authorize( HttpServer.Request request, Map securityMap ) throws SampException { JComponent panel = Box.createVerticalBox(); JComponent linePic = Box.createHorizontalBox(); URL imageUrl = Client.class.getResource( "images/danger.jpeg" ); linePic.add( Box.createHorizontalGlue() ); linePic.add( new JLabel( new ImageIcon( imageUrl ) ) ); linePic.add( Box.createHorizontalGlue() ); panel.add( linePic ); panel.add( Box.createVerticalStrut( 5 ) ); JComponent line1 = Box.createHorizontalBox(); String appName = ClientAuthorizers.getAppName( securityMap ); line1.add( new JLabel( "Client \"" + appName + "\" is requesting Web Profile registration." ) ); line1.add( Box.createHorizontalGlue() ); line1.setBorder( createBorder( false ) ); panel.add( line1 ); JLabel deathLabel = new JLabel( "CERTAIN DEATH" ); deathLabel.setForeground( Color.RED ); deathLabel.setFont( deathLabel.getFont() .deriveFont( deathLabel.getFont().getSize() + 2f ) ); JComponent line2 = Box.createHorizontalBox(); line2.add( new JLabel( "Accepting this request will lead to " ) ); line2.add( deathLabel ); line2.add( new JLabel( "!" ) ); line2.add( Box.createHorizontalGlue() ); line2.setBorder( createBorder( true ) ); panel.add( line2 ); if ( JOptionPane .showOptionDialog( parent_, panel, "Registration Request", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, IconStore.createEmptyIcon( 0 ), new String[] { "Accept", "Reject" }, "Reject" ) != 0 ) { throw new SampException( "User denied authorization as advised" ); } } /** * Returns a new border of fixed dimensions which may or may not include * an element of highlighting. * * @param highlight true to highlight border * @return new border */ private Border createBorder( boolean highlight ) { Color color = new Color( 0x00ff0000, ! highlight ); return BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( new Color( 0xff0000, !highlight ) ), BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/HubSwingClientAuthorizer.java000066400000000000000000000210661356450004300275700ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.awt.Component; import java.awt.Dimension; import java.awt.GraphicsEnvironment; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.HeadlessException; import java.awt.Insets; import java.awt.Window; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.logging.Logger; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.HttpServer; /** * ClientAuthorizer implementation that queries the user for permission * via a popup dialogue. * * @author Mark Taylor * @since 2 Feb 2011 */ public class HubSwingClientAuthorizer implements ClientAuthorizer { private final Component parent_; private final CredentialPresenter presenter_; private static final int MAX_POPUP_WIDTH = 500; private static final Logger logger_ = Logger.getLogger( HubSwingClientAuthorizer.class.getName() ); /** * Constructor. * * @param parent parent component * @param presenter handles credential presentation to the user */ public HubSwingClientAuthorizer( Component parent, CredentialPresenter presenter ) { parent_ = parent; presenter_ = presenter; if ( GraphicsEnvironment.isHeadless() ) { throw new HeadlessException( "Client authorization dialogues " + "impossible - no graphics" ); } } public void authorize( HttpServer.Request request, Map securityMap ) throws SampException { // Prepare an internationalised query dialogue. AuthResourceBundle.Content authContent = AuthResourceBundle .getAuthContent( ResourceBundle .getBundle( AuthResourceBundle.class.getName() ) ); Object[] qmsg = getMessageLines( request, securityMap, authContent ); String noOpt = authContent.noWord(); String yesOpt = authContent.yesWord(); // Just calling showOptionDialog can end up with the popup being // obscured by other windows on the desktop, at least for win XP. JOptionPane jop = new JOptionPane( qmsg, JOptionPane.WARNING_MESSAGE, JOptionPane.YES_NO_OPTION, null, new String[] { noOpt, yesOpt }, noOpt ); JDialog dialog = jop.createDialog( parent_, authContent.windowTitle() ); attemptSetAlwaysOnTop( dialog, true ); dialog.setModal( true ); dialog.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE ); // It seems to be OK to call Dialog.setVisible on a modal dialogue // from threads other than the AWT Event Dispatch Thread. // I admit though that I haven't seen document which assures that // this is true however. dialog.setVisible( true ); dialog.dispose(); if ( jop.getValue() != yesOpt ) { throw new SampException( "User denied authorization" ); } } /** * Returns a "message" object describing the applying client to the user. * The return value is suitable for use as the msg argument * of one of JOptionPane's methods. * * @param request HTTP request bearing the application * @param securityMap information supplied explicitly by application * @param authContent content of AuthResourceBundle bundle * @return message array describing the applicant to the user * @throws SampExecution if registration is to be rejected out of hand * @see javax.swing.JOptionPane */ private Object[] getMessageLines( HttpServer.Request request, Map securityMap, AuthResourceBundle.Content authContent ) throws SampException { Map headerMap = request.getHeaderMap(); CredentialPresenter.Presentation presentation = presenter_.createPresentation( request, securityMap, authContent ); List lineList = new ArrayList(); lineList.addAll( toLineList( authContent.appIntroductionLines() ) ); lineList.add( "\n" ); lineList.add( createLabelledFields( presentation.getAuthEntries(), authContent.undeclaredWord() ) ); lineList.add( "\n" ); Object[] lines = presentation.getAuthLines(); lineList.addAll( Arrays.asList( lines ) ); if ( lines.length > 0 ) { lineList.add( "\n" ); } lineList.addAll( toLineList( authContent.privilegeWarningLines() ) ); lineList.add( "\n" ); lineList.addAll( toLineList( authContent.adviceLines() ) ); lineList.add( "\n" ); lineList.add( authContent.questionLine() ); return lineList.toArray(); } /** * Returns a component displaying name/value pairs represented by * a given String->String map. * * @param infoMap String->String map of key->value pairs * @param undeclaredWord text to use to indicate a null value * @return display component */ private JComponent createLabelledFields( Map infoMap, String undeclaredWord ) { GridBagLayout layer = new GridBagLayout(); JComponent box = new JPanel( layer ) { public Dimension getPreferredSize() { Dimension size = super.getPreferredSize(); return new Dimension( Math.min( size.width, MAX_POPUP_WIDTH ), size.height ); } }; GridBagConstraints keyCons = new GridBagConstraints(); GridBagConstraints valCons = new GridBagConstraints(); keyCons.gridy = 0; valCons.gridy = 0; keyCons.gridx = 0; valCons.gridx = 1; keyCons.anchor = GridBagConstraints.WEST; valCons.anchor = GridBagConstraints.WEST; keyCons.fill = GridBagConstraints.NONE; valCons.fill = GridBagConstraints.HORIZONTAL; keyCons.weighty = 1; valCons.weighty = 1; keyCons.weightx = 0; valCons.weightx = 1; valCons.insets = new Insets( 1, 1, 1, 1 ); JComponent stack = Box.createVerticalBox(); for ( Iterator it = infoMap.keySet().iterator(); it.hasNext(); ) { String key = (String) it.next(); String value = (String) infoMap.get( key ); String valtxt = value == null ? undeclaredWord : value; JComponent keyComp = new JLabel( key + ": " ); JTextField valueField = new JTextField( valtxt ); valueField.setEditable( false ); layer.setConstraints( keyComp, keyCons ); layer.setConstraints( valueField, valCons ); box.add( keyComp ); box.add( valueField ); keyCons.gridy++; valCons.gridy++; } box.setBorder( BorderFactory.createEmptyBorder( 0, 0, 0, 0 ) ); return box; } /** * Turns a multi-line string into an array of strings. * * @param linesTxt string perhaps with embedded \n characters * @return array of lines */ private static String[] toLines( String linesTxt ) { return linesTxt.split( "\\n" ); } /** * Turns a multi-line string into a List of strings. * * @param linesTxt string perhaps with embedded \n characters * @return list of String lines */ private static List toLineList( String linesTxt ) { return Arrays.asList( toLines( linesTxt ) ); } /** * Tries to set the always-on-top property of a window. * This is only possible in JRE1.5 and later, so it's done here by * reflection. If it fails, a logging message is emitted. * * @param win window to set * @param isOnTop true for on top, false for not */ private static void attemptSetAlwaysOnTop( Window win, boolean isOnTop ) { try { Window.class.getMethod( "setAlwaysOnTop", new Class[] { boolean.class } ) .invoke( win, new Object[] { Boolean.valueOf( isOnTop ) } ); } catch ( Throwable e ) { logger_.info( "Can't set window on top, not J2SE5" ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/ListMessageRestriction.java000066400000000000000000000115771356450004300273020ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.Map; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.hub.MessageRestriction; /** * General purpose implementation of MessageRestriction. * It allows to either whitelist or blacklist a given list of MType * patterns, with the option for client subscriptions to override * this policy by setting the "x-samp.mostly-harmless" key in the * annotation map corresponding to a given MType subscription. * * @author Mark Taylor * @since 23 Nov 2011 */ public class ListMessageRestriction implements MessageRestriction { private final boolean allow_; private final boolean useSubsInfo_; private final Subscriptions subs_; /** * Default list of MType patterns returned by {@link #getSafeMTypes}. */ public static String[] DEFAULT_SAFE_MTYPES = new String[] { "samp.app.*", "samp.msg.progress", "table.*", "image.*", "coord.*", "spectrum.*", "bibcode.*", "voresource.*", }; /** * System property used to specify a default list of known safe MTypes, * which the {@link #DEFAULT} policy will permit. * The value is a comma-separated list of MType patterns. */ public static final String SAFE_MTYPE_PROP = "jsamp.mtypes.safe"; /** * Default MessageRestriction implementation. * The current implementation allows a list of MTypes believed to be safe, * as given by calling {@link #getSafeMTypes}, and blocks all others. * However, client subscriptions may override this by annotating their * subscriptions with an entry having the key * "x-samp.mostly-harmless". * If this has the value "1" the MType thus annotated is allowed, * and if it has the value "0" it is blocked, regardless of the safe list. */ public static final MessageRestriction DEFAULT = new ListMessageRestriction( true, getSafeMTypes(), true ); /** * MessageRestriction that permits all MTypes, except as overridden * by x-samp.mostly-harmless annotations. */ public static final MessageRestriction ALLOW_ALL = new ListMessageRestriction( false, new String[ 0 ], true ) { public String toString() { return "ALLOW_ALL"; } }; /** * MessageRestriction that blocks all MTypes, except as overridden * by x-samp.mostly-harmless annotations. */ public static final MessageRestriction DENY_ALL = new ListMessageRestriction( true, new String[ 0 ], true ) { public String toString() { return "DENY_ALL"; } }; /** * Constructor. * * @param allow whether the sense of the mtypes list is those * that should be allowed (true) or blocked (false) * @param mtypes mtype patterns to be allowed or blocked * @param useSubsInfo if true, honour x-samp.mostly-harmless * subscription annotations */ public ListMessageRestriction( boolean allow, String[] mtypes, boolean useSubsInfo ) { allow_ = allow; useSubsInfo_ = useSubsInfo; subs_ = new Subscriptions(); for ( int im = 0; im < mtypes.length; im++ ) { subs_.addMType( mtypes[ im ] ); } } public boolean permitSend( String mtype, Map subsInfo ) { if ( useSubsInfo_ ) { Object markedHarmless = subsInfo.get( "samp.mostly-harmless" ); if ( markedHarmless == null ) { markedHarmless = subsInfo.get( "x-samp.mostly-harmless" ); } if ( "0".equals( markedHarmless ) ) { return false; } else if ( "1".equals( markedHarmless ) ) { return true; } } boolean knownSafe = ( ! allow_ ) ^ subs_.isSubscribed( mtype ); return knownSafe; } public String toString() { StringBuffer sbuf = new StringBuffer() .append( allow_ ? "Allow" : "Deny" ) .append( ' ' ) .append( subs_.keySet() ); if ( useSubsInfo_ ) { sbuf.append( "; " ) .append( "honour (x-)samp.mostly-harmless" ); } return sbuf.toString(); } /** * Returns a list of MType patterns which are permitted by the DEFAULT * policy. If the System Property {@value SAFE_MTYPE_PROP} exists, * its value is taken as a comma-separated list of known permitted MType * patterns. Otherwise, the {@link #DEFAULT_SAFE_MTYPES} array is returned. * * @return list of MTypes treated as harmless by default */ public static String[] getSafeMTypes() { String safeMtypes = System.getProperty( SAFE_MTYPE_PROP ); if ( safeMtypes == null ) { return DEFAULT_SAFE_MTYPES; } else { return safeMtypes.split( "," ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/LoggingCorsHttpServer.java000066400000000000000000000177611356450004300271010ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.net.ServerSocket; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.util.Iterator; import java.util.Map; /** * CorsHttpServer subclass which performs logging to a given print stream * at the HTTP level. Logging is not done through the logging system. * * @author Mark Taylor * @since 2 Feb 2011 */ public class LoggingCorsHttpServer extends CorsHttpServer { private final PrintStream out_; private int iSeq_; /** * Constructor. * * @param socket socket hosting the service * @param auth defines which domains requests will be permitted from * @param out destination stream for logging */ public LoggingCorsHttpServer( ServerSocket socket, OriginAuthorizer auth, PrintStream out ) throws IOException { super( socket, auth ); out_ = out; } public Response serve( Request request ) { int iseq; synchronized ( this ) { iseq = ++iSeq_; } logRequest( request, iseq ); return new LoggedResponse( super.serve( request ), iseq, "POST".equals( request.getMethod() ) ); } /** * Logs a given request. * * @param request HTTP request * @param iseq index of the request; unique integer for each request */ private void logRequest( Request request, int iseq ) { StringBuffer sbuf = new StringBuffer(); sbuf.append( '\n' ); appendBanner( sbuf, '>', iseq ); sbuf.append( request.getMethod() ) .append( ' ' ) .append( request.getUrl() ) .append( '\n' ); appendHeaders( sbuf, request.getHeaderMap() ); byte[] body = request.getBody(); if ( body != null && body.length > 0 ) { sbuf.append( '\n' ); try { sbuf.append( new String( request.getBody(), "utf-8" ) ); } catch ( UnsupportedEncodingException e ) { throw new AssertionError( "No utf-8??" ); } } out_.println( sbuf ); } /** * Adds a line to the given stringbuffer which indicates information * relating to a given sequence number follows. * * @param sbuf string buffer to add to * @param c filler character * @param iseq sequence number */ private void appendBanner( StringBuffer sbuf, char c, int iseq ) { String label = Integer.toString( iseq ); int nc = 75 - label.length(); for ( int i = 0; i < nc; i++ ) { sbuf.append( c ); } sbuf.append( ' ' ) .append( label ) .append( '\n' ); } /** * Adds HTTP header information to a string buffer. * * @param sbuf buffer to add lines to * @param map header key->value pair map */ private void appendHeaders( StringBuffer sbuf, Map map ) { for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); sbuf.append( entry.getKey() ) .append( ": " ) .append( entry.getValue() ) .append( '\n' ); } } /** * HTTP response which will log its content at an appropriate time. */ private class LoggedResponse extends Response { private final Response base_; private final boolean logBody_; private final String headText_; private String bodyText_; /** * Constructor. * * @param base response on which this one is based * @param iseq sequence number of request that this is a response to * @param logBody true iff the body of the response is to be logged */ LoggedResponse( Response base, int iseq, boolean logBody ) { super( base.getStatusCode(), base.getStatusPhrase(), base.getHeaderMap() ); base_ = base; logBody_ = logBody; StringBuffer sbuf = new StringBuffer(); sbuf.append( '\n' ); appendBanner( sbuf, '<', iseq ); sbuf.append( getStatusCode() ) .append( ' ' ) .append( getStatusPhrase() ) .append( '\n' ); appendHeaders( sbuf, getHeaderMap() ); headText_ = sbuf.toString(); } public void writeBody( final OutputStream out ) throws IOException { // This method captures the logging output as well as writing // the body text to the given stream. The purpose of doing it // like this rather than writing the logging information // directly is so that this method can be harmlessly called // multiple times (doesn't normally happen, but can do sometimes). // Prepare an object (an OutputStream) to which you can write // the body content and then use its toString method to get // the loggable text. This loggable text is either the // body content itself, or an indication of how many bytes it // contained, depending on the logBody_ flag. final OutputStream lout = logBody_ ? (OutputStream) new ByteArrayOutputStream() { public String toString() { String txt; try { txt = new String( buf, 0, count, "utf-8" ); } catch ( UnsupportedEncodingException e ) { txt = e.toString(); } return "\n" + txt + "\n"; } } : (OutputStream) new CountOutputStream() { public String toString() { return count_ > 0 ? "<" + count_ + " bytes of output omitted>\n" : ""; } }; // Prepare an output stream which writes both to the normal // response destination and to the content logging object we've // just set up. OutputStream teeOut = new OutputStream() { public void write( byte[] b ) throws IOException { lout.write( b ); out.write( b ); } public void write( byte[] b, int off, int len ) throws IOException { lout.write( b, off, len ); out.write( b, off, len ); } public void write( int b ) throws IOException { lout.write( b ); out.write( b ); } }; // Write the body content to the response output stream, // and store the loggable output. String slog; try { base_.writeBody( teeOut ); slog = lout.toString(); } catch ( IOException e ) { slog = "log error? " + e + "\n"; } bodyText_ = slog; } public void writeResponse( final OutputStream out ) throws IOException { super.writeResponse( out ); out_.print( headText_ + bodyText_ ); } } /** * OutputStream subclass which counts the number of bytes it is being * asked to write, but otherwise does nothing. */ private static class CountOutputStream extends OutputStream { long count_; // number of bytes counted so far public void write( byte[] b ) { count_ += b.length; } public void write( byte[] b, int off, int len ) { count_ += len; } public void write( int b ) { count_++; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/OpenPolicyResourceHandler.java000066400000000000000000000154511356450004300277160ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.LinkedHashMap; import java.util.Map; import org.astrogrid.samp.httpd.HttpServer; import org.astrogrid.samp.httpd.ServerResource; /** * HTTP resource handler suitable for serving static cross-origin policy files. * * @author Mark Taylor * @since 2 Feb 2011 */ public class OpenPolicyResourceHandler implements HttpServer.Handler { private final String policyPath_; private final ServerResource policyResource_; private final OriginAuthorizer authorizer_; private final HttpServer.Response response405_; /** * Constructor. * * @param policyPath path at which the policy file will reside on * this handler's server * @param policyResource content of policy file * @param authorizer controls who is permitted to view the policy file */ public OpenPolicyResourceHandler( String policyPath, ServerResource policyResource, OriginAuthorizer authorizer ) { policyPath_ = policyPath; policyResource_ = policyResource; authorizer_ = authorizer; response405_ = HttpServer.create405Response( new String[] { "GET", "HEAD", } ); } public HttpServer.Response serveRequest( HttpServer.Request request ) { if ( request.getUrl().equals( policyPath_ ) ) { String method = request.getMethod(); if ( ! method.equals( "HEAD" ) && ! method.equals( "GET" ) ) { return response405_; } else if ( authorizer_.authorizeAll() ) { Map hdrMap = new LinkedHashMap(); hdrMap.put( "Content-Type", policyResource_.getContentType() ); long contentLength = policyResource_.getContentLength(); if ( contentLength >= 0 ) { hdrMap.put( "Content-Length", Long.toString( contentLength ) ); } if ( method.equals( "HEAD" ) ) { return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) { } }; } else if ( method.equals( "GET" ) ) { return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) throws IOException { policyResource_.writeBody( out ); } }; } else { assert false; return response405_; } } else { return HttpServer.createErrorResponse( 404, "Not found" ); } } else { return null; } } /** * Creates a handler suitable for serving static cross-origin policy files. * * @param path path at which the policy file will reside on the * handler's HTTP server * @param contentUrl external URL at which the resource contents * can be found; this will be retrieved once and * cached * @param oAuth controls who is permitted to retrieve the policy file */ public static HttpServer.Handler createPolicyHandler( String path, URL contentUrl, String contentType, OriginAuthorizer oAuth ) throws IOException { ServerResource resource = createCachedResource( contentUrl, contentType ); return new OpenPolicyResourceHandler( path, resource, oAuth ); } /** * Returns a handler which can serve the /crossdomain.xml file * used by Adobe Flash. The policy file permits access from anywhere. * * @param oAuth controls who is permitted to retrieve the policy file * @return policy file handler * @see Adobe Flash cross-origin policy */ public static HttpServer.Handler createFlashPolicyHandler( OriginAuthorizer oAuth ) throws IOException { return createPolicyHandler( "/crossdomain.xml", OpenPolicyResourceHandler.class .getResource( "crossdomain.xml" ), "text/x-cross-domain-policy", oAuth ); } /** * Returns a handler which can serve the /clientaccesspolicy.xml file * used by Microsoft Silverlight. The policy file permits access * from anywhere. * * @param oAuth controls who is permitted to retrieve the policy file * @return policy file handler * @see MS Silverlight cross-origin policy */ public static HttpServer.Handler createSilverlightPolicyHandler( OriginAuthorizer oAuth ) throws IOException { return createPolicyHandler( "/clientaccesspolicy.xml", OpenPolicyResourceHandler.class .getResource( "clientaccesspolicy.xml" ), "text/xml", oAuth ); } /** * Returns a ServerResource which caches the contents of a given * (presumably smallish and unchanging) external resource. * * @param dataUrl location of external resource * @param contentType MIME type for content of dataUrl * @return new cached resource representing content of * dataUrl */ private static ServerResource createCachedResource( URL dataUrl, final String contentType ) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); InputStream uin = dataUrl.openStream(); byte[] buf = new byte[ 1024 ]; for ( int count; ( count = uin.read( buf ) ) >= 0; ) { bout.write( buf, 0, count ); } bout.close(); final byte[] data = bout.toByteArray(); return new ServerResource() { public long getContentLength() { return data.length; } public String getContentType() { return contentType; } public void writeBody( OutputStream out ) throws IOException { out.write( data ); } }; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/OriginAuthorizer.java000066400000000000000000000014241356450004300261260ustar00rootroot00000000000000package org.astrogrid.samp.web; /** * Controls which origins are authorized to perform cross-origin access * to resources. * * @author Mark Taylor * @since 2 Feb 2011 */ public interface OriginAuthorizer { /** * Indicates whether a client with a given origin is permitted * to access resources. * * @param origin client Origin * @return true iff access is permitted * @see Web Origin concept */ boolean authorize( String origin ); /** * Indicates whether clients from arbitrary origins (including none) * are permitted to access resources. * * @return true iff access is permitted */ boolean authorizeAll(); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/OriginAuthorizers.java000066400000000000000000000112761356450004300263170ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * Utility class containing OriginAuthorizer implementations. * * @author Mark Taylor * @since 2 Feb 2011 */ public class OriginAuthorizers { /** OriginAuthorizer which always denies access. */ public static final OriginAuthorizer FALSE = createFixedOriginAuthorizer( false, false ); /** OriginAuthorizer which always permits access. */ public static final OriginAuthorizer TRUE = createFixedOriginAuthorizer( true, true ); /** OriginAuthorizer which queries the user via a popup dialogue. */ public static final OriginAuthorizer SWING = createMemoryOriginAuthorizer( createLoggingOriginAuthorizer( new SwingOriginAuthorizer( null ), Level.INFO, Level.WARNING ) ); private static final Logger logger_ = Logger.getLogger( OriginAuthorizers.class.getName() ); /** * Private constructor prevents instantiation. */ private OriginAuthorizers() { } /** * Returns an OriginAuthorizer with fixed responses, regardless of input. * * @param individualPolicy invariable response of * authorize method * @param generalPolicy invariable response of * authorizeAll method */ public static OriginAuthorizer createFixedOriginAuthorizer( final boolean individualPolicy, final boolean generalPolicy ) { return new OriginAuthorizer() { public boolean authorize( String origin ) { return individualPolicy; } public boolean authorizeAll() { return generalPolicy; } }; } /** * Returns an OriginAuthorizer based on an existing one which logs * responses. * * @param auth base authorizer * @param acceptLevel level at which acceptances will be logged * @param refuseLevel level at which refusals will be logged */ public static OriginAuthorizer createLoggingOriginAuthorizer( final OriginAuthorizer auth, final Level acceptLevel, final Level refuseLevel ) { return new OriginAuthorizer() { public synchronized boolean authorize( String origin ) { boolean accept = auth.authorize( origin ); log( accept, "\"" + origin + "\"" ); return accept; } public synchronized boolean authorizeAll() { boolean accept = auth.authorizeAll(); log( accept, "all origins" ); return accept; } private void log( boolean accept, String domain ) { if ( accept ) { logger_.log( acceptLevel, "Accepted cross-origin requests for " + domain ); } else { logger_.log( refuseLevel, "Rejected cross-origin requests for " + domain ); } } }; } /** * Returns an OriginAuthorizer based on an existing one which caches * responses. * * @param auth base authorizer */ public static OriginAuthorizer createMemoryOriginAuthorizer( final OriginAuthorizer auth ) { return new OriginAuthorizer() { private final OriginAuthorizer baseAuth_ = auth; private final Set acceptedSet_ = new HashSet(); private final Set refusedSet_ = new HashSet(); private Boolean authorizeAll_; public synchronized boolean authorize( String origin ) { if ( refusedSet_.contains( origin ) ) { return false; } else if ( acceptedSet_.contains( origin ) ) { return true; } else { boolean accepted = baseAuth_.authorize( origin ); ( accepted ? acceptedSet_ : refusedSet_ ).add( origin ); return accepted; } } public synchronized boolean authorizeAll() { if ( authorizeAll_ == null ) { authorizeAll_ = Boolean.valueOf( baseAuth_.authorizeAll() ); } return authorizeAll_.booleanValue(); } }; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/SwingOriginAuthorizer.java000066400000000000000000000031051356450004300271340ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.awt.Component; import javax.swing.JOptionPane; /** * OriginAuthorizer which uses a popup dialogue to ask the user. * * @author Mark Taylor * @since 2 Feb 2011 */ class SwingOriginAuthorizer implements OriginAuthorizer { private final Component parent_; /** * Constructor. * * @param parent parent component */ public SwingOriginAuthorizer( Component parent ) { parent_ = parent; } public boolean authorize( String origin ) { return getResponse( new String[] { "Is the following origin authorized for cross-domain HTTP access?", " " + origin, } ); } public boolean authorizeAll() { return getResponse( new String[] { "Are all origins authorized for cross-domain HTTP access?", } ); } /** * Presents some lines of text to the user and solicits a yes/no * response from them. * This method does not need to be called from the AWT event dispatch * thread. * * @param lines lines of formatted plain text * (not too many; not too long) * @return true/false for use yes/no response */ protected boolean getResponse( String[] lines ) { return JOptionPane .showOptionDialog( parent_, lines, "Security", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[] { "Yes", "No" }, "No" ) == 0; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/UrlTracker.java000066400000000000000000000124001356450004300246740ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * Keeps track of which URLs have been seen in communications inbound to * and outbound from Web Profile clients. * On the basis of these observations it is able to advise whether a * Web Profile client ought to be permitted to dereference a given URL. * The idea is that a Web Profile client, which may not be entirely * trustworthy, has no legitimate reason for dereferencing an arbitrary * URL, and should only be permitted to dereference local URLs if they * have previously been sent as message arguments to it. * (so for instance an attempt to read file:///etc/password is likely to * be blocked). * Since a SAMP client may be able to provoke some kind of echo, any * URL which was mentioned by a Web Profile client before any other * client mentions it is automatically marked as suspicious. * *

Details of the implementation are arguable. * * @author Mark Taylor * @since 22 Jul 2011 */ public class UrlTracker { private final Set permittedSet_; private final Set blockedSet_; private final String[] localhostNames_; private final Logger logger_ = Logger.getLogger( UrlTracker.class.getName() ); /** * Constructor. */ public UrlTracker() { permittedSet_ = new HashSet(); blockedSet_ = new HashSet(); // Set up a list of aliases for the local host so we can identify // and prepare to restrict access to localhost URLs. List localNameList = new ArrayList(); localNameList.add( "localhost" ); localNameList.add( "127.0.0.1" ); try { InetAddress localAddr = InetAddress.getLocalHost(); localNameList.add( localAddr.getHostName() ); localNameList.add( localAddr.getHostAddress() ); localNameList.add( localAddr.getCanonicalHostName() ); } catch ( UnknownHostException e ) { logger_.log( Level.WARNING, "Can't determine local host name", e ); } localhostNames_ = (String[]) localNameList.toArray( new String[ 0 ] ); } /** * Note that a URL has been communicated to a Web Profile client * from the outside world. * * @param url incoming URL */ public synchronized void noteIncomingUrl( URL url ) { if ( isSensitive( url ) ) { if ( ! blockedSet_.contains( url ) ) { if ( permittedSet_.add( url ) ) { logger_.config( "Mark for translate permission URL " + url ); } } } } /** * Note that a Web Profile client has communicated a URL to the * outside world. * * @param url outgoing URL */ public synchronized void noteOutgoingUrl( URL url ) { if ( isSensitive( url ) ) { if ( ! permittedSet_.contains( url ) ) { if ( blockedSet_.add( url ) ) { logger_.config( "Mark for translate blocking URL " + url ); } } } } /** * Indicates whether access to a given URL should be permitted, * according to the strategy implemented by this class, * from a Web Profile client. * * @param url URL to assess * @return true iff permission to access is appropriate */ public synchronized boolean isUrlPermitted( URL url ) { if ( isSensitive( url ) ) { if ( permittedSet_.contains( url ) ) { logger_.config( "Translation permitted for marked URL " + url ); return true; } else { logger_.warning( "Translation denied for unmarked URL " + url ); return false; } } else { logger_.config( "Translation permitted for non-sensitive URL " + url ); return true; } } /** * Indicates whether a given URL is potentially sensitive. * The current implementation always returns true. * This is probably correct, since it's not in general possible * to tell whether or not a given URL accords privileges to * requests from the local host. But if this ends up letting * too much through, identifying only file URLs and http/https * ones on the local domain would probably be OK. * * @param url URL to assess * @return true iff access should be restricted */ protected boolean isSensitive( URL url ) { return true; } /** * Determines whether a hostname appears to reference the localhost. * * @param host hostname from URL * @return true iff host appears to be, or may be, local */ private boolean isLocalHost( String host ) { if ( host == null || host.length() == 0 ) { return true; } for ( int i = 0; i < localhostNames_.length; i++ ) { if ( host.equalsIgnoreCase( localhostNames_[ i ] ) ) { return true; } } return false; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/UrlTrackerHubConnection.java000066400000000000000000000175161356450004300273700ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * HubConnection wrapper implementation which intercepts all incoming * and outgoing communications, scans them for URLs in the payload, * and notifies a supplied UrlTracker object. * * @author Mark Taylor * @since 22 Jul 2011 */ class UrlTrackerHubConnection implements HubConnection { private final HubConnection base_; private final UrlTracker urlTracker_; /** * Constructor. * * @param base connection on which this one is based * @param urlTracker tracker for URL usage */ public UrlTrackerHubConnection( HubConnection base, UrlTracker urlTracker ) { base_ = base; urlTracker_ = urlTracker; } /** * Recursively scans a SAMP map for items that look like URLs, * and notifies the tracker that they are incoming. * As a convenience the input map is returned. * * @param map map to scan * @return the input map, unchanged */ private Map scanIncoming( Map map ) { URL[] urls = scanForUrls( map ); for ( int iu = 0; iu < urls.length; iu++ ) { urlTracker_.noteIncomingUrl( urls[ iu ] ); } return map; } /** * Recursively scans a SAMP map for items that look like URLs, * and notifies the tracker that they are outgoing. * As a convenience the input map is returned. * * @param map map to scan * @return the input map, unchanged */ private Map scanOutgoing( Map map ) { URL[] urls = scanForUrls( map ); for ( int iu = 0; iu < urls.length; iu++ ) { urlTracker_.noteOutgoingUrl( urls[ iu ] ); } return map; } /** * Recursively scans a map for items that look like URLs and * returns them as an array. * * @param map map to scan * @return array of URLs found in map */ private URL[] scanForUrls( Map map ) { Collection urlSet = new HashSet(); scanForUrls( map, urlSet ); return (URL[]) urlSet.toArray( new URL[ 0 ] ); } /** * Recursively scans a SAMP data item for items that look like URLs * and appends them into a supplied list. * * @param item SAMP data item (String, List or Map) * @param urlSet list of URL objects to which URLs can be added */ private void scanForUrls( Object item, Collection urlSet ) { if ( item instanceof String ) { if ( isUrl( (String) item ) ) { try { urlSet.add( new URL( (String) item ) ); } catch ( MalformedURLException e ) { } } } else if ( item instanceof List ) { for ( Iterator it = ((List) item).iterator(); it.hasNext(); ) { scanForUrls( it.next(), urlSet ); } } else if ( item instanceof Map ) { for ( Iterator it = ((Map) item).values().iterator(); it.hasNext(); ) { scanForUrls( it.next(), urlSet ); } } } /** * Determines whether a given string is apparently a URL. * * @param str string to test * @return true iff str looks like a URL */ private boolean isUrl( String str ) { if ( str == null || str.indexOf( ":/" ) <= 0 ) { return false; } else { try { new URL( str ); return true; } catch ( MalformedURLException e ) { return false; } } } public void setCallable( CallableClient callable ) throws SampException { base_.setCallable( new UrlTrackerCallableClient( callable ) ); } public void notify( String recipientId, Map msg ) throws SampException { base_.notify( recipientId, scanOutgoing( msg ) ); } public List notifyAll( Map msg ) throws SampException { return base_.notifyAll( scanOutgoing( msg ) ); } public String call( String recipientId, String msgTag, Map msg ) throws SampException { return base_.call( recipientId, msgTag, scanOutgoing( msg ) ); } public Map callAll( String msgTag, Map msg ) throws SampException { return base_.callAll( msgTag, scanOutgoing( msg ) ); } public Response callAndWait( String recipientId, Map msg, int timeout ) throws SampException { return (Response) scanIncoming( base_.callAndWait( recipientId, scanOutgoing( msg ), timeout ) ); } public void reply( String msgId, Map response ) throws SampException { base_.reply( msgId, scanOutgoing( response ) ); } public RegInfo getRegInfo() { return (RegInfo) scanIncoming( base_.getRegInfo() ); } public void ping() throws SampException { base_.ping(); } public void unregister() throws SampException { base_.unregister(); } public void declareMetadata( Map meta ) throws SampException { base_.declareMetadata( scanOutgoing( meta ) ); } public Metadata getMetadata( String clientId ) throws SampException { return (Metadata) scanIncoming( base_.getMetadata( clientId ) ); } public void declareSubscriptions( Map subs ) throws SampException { base_.declareSubscriptions( scanOutgoing( subs ) ); } public Subscriptions getSubscriptions( String clientId ) throws SampException { return (Subscriptions) scanIncoming( base_.getSubscriptions( clientId ) ); } public String[] getRegisteredClients() throws SampException { return base_.getRegisteredClients(); } public Map getSubscribedClients( String mtype ) throws SampException { return scanIncoming( base_.getSubscribedClients( mtype ) ); } /** * CallableClient wrapper implementation which intercepts * communications, scans the payloads for URLs, and informs an * associated UrlTracker. */ private class UrlTrackerCallableClient implements CallableClient { private final CallableClient baseCallable_; /** * Constructor. * * @param baseCallable object on which this one is based */ UrlTrackerCallableClient( CallableClient baseCallable ) { baseCallable_ = baseCallable; } public void receiveCall( String senderId, String msgId, Message msg ) throws Exception { baseCallable_.receiveCall( senderId, msgId, (Message) scanIncoming( msg ) ); } public void receiveNotification( String senderId, Message msg ) throws Exception { baseCallable_.receiveNotification( senderId, (Message) scanIncoming( msg ) ); } public void receiveResponse( String responderId, String msgTag, Response response ) throws Exception { baseCallable_.receiveResponse( responderId, msgTag, (Response) scanIncoming( response ) ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebCallableClient.java000066400000000000000000000077541356450004300261320ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.SampException; /** * CallableClient implementation used internally by the Web Profile hub. * * @author Mark Taylor * @since 2 Feb 2011 */ class WebCallableClient implements CallableClient { private final List queue_; private final int capacity_; private boolean ended_; /** Default maximum for queued callbacks. */ public final static int DEFAULT_CAPACITY = 4096; /** * Constructs a callable client with default maximum capacity. */ public WebCallableClient() { this( DEFAULT_CAPACITY ); } /** * Constructs a callable client with a given maximum callback capacity. * * @param capacity maximum number of queued callbacks */ public WebCallableClient( int capacity ) { capacity_ = capacity; queue_ = new ArrayList(); } /** * Blocks for up to a given number of seconds or until any callbacks * are ready, then returns any ready callbacks. * * @param timeout timeout in seconds * @return list of {@link Callback}-like Maps */ public List pullCallbacks( int timeout ) throws SampException { // Calculate the timeout epoch as currentTimeMillis. long end = timeout >= 0 ? System.currentTimeMillis() + timeout * 1000 : Long.MAX_VALUE; // Wait until either there are callbacks or timeout is reached. try { synchronized ( queue_ ) { while ( queue_.isEmpty() && end - System.currentTimeMillis() > 0 && ! ended_ ) { queue_.wait( end - System.currentTimeMillis() ); } // Remove available callbacks from the queue, if any, // and return them. List callbacks = new ArrayList( queue_ ); queue_.clear(); return callbacks; } } catch ( InterruptedException e ) { throw new SampException( "Interrupted", e ); } } public void receiveNotification( String senderId, Message message ) { enqueue( "receiveNotification", new Object[] { senderId, message } ); } public void receiveCall( String senderId, String msgId, Message message ) { enqueue( "receiveCall", new Object[] { senderId, msgId, message } ); } public void receiveResponse( String responderId, String msgTag, Response response ) { enqueue( "receiveResponse", new Object[] { responderId, msgTag, response } ); } /** * Informs this client that no further callbacks (receive* methods) * will be made on it. */ public void endCallbacks() { ended_ = true; synchronized ( queue_ ) { queue_.notifyAll(); } } /** * Adds a new callback to the queue which can be passed out via the * {@link #pullCallbacks} method. * * @param methodName callback method name * @param params callback parameter list */ private void enqueue( String methodName, Object[] params ) { Callback callback = new Callback( WebClientProfile.WEBSAMP_CLIENT_PREFIX + methodName, Arrays.asList( params ) ); callback.check(); synchronized ( queue_ ) { if ( queue_.size() < capacity_ ) { queue_.add( callback ); queue_.notifyAll(); } else { throw new IllegalStateException( "Callback queue is full" + " (" + capacity_ + " objects)" ); } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebClientProfile.java000066400000000000000000000137671356450004300260340ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.io.IOException; import java.net.ConnectException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Platform; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.xmlrpc.SampXmlRpcClient; import org.astrogrid.samp.xmlrpc.SampXmlRpcClientFactory; import org.astrogrid.samp.xmlrpc.XmlRpcKit; /** * ClientProfile implementation for Web Profile. * * @author Mark Taylor * @since 3 Feb 2011 */ public class WebClientProfile implements ClientProfile { private final SampXmlRpcClientFactory xClientFactory_; private final Map securityMap_; private final URL hubEndpoint_; /** Web Profile hub port number ({@value}). */ public static final int WEBSAMP_PORT = 21012; /** * Path on WEBSAMP_PORT web server at which XML-RPC server lives * ({@value}). */ public static final String WEBSAMP_PATH = "/"; /** * Prefix to hub interface operation names for XML-RPC method names * ({@value}). */ public static final String WEBSAMP_HUB_PREFIX = "samp.webhub."; /** * Prefix to client interface opeation names for XML-RPC method names * ({@value}). */ public static final String WEBSAMP_CLIENT_PREFIX = ""; /** * RegInfo map key for URL translation service base URL * ({@value}). */ public static final String URLTRANS_KEY = "samp.url-translator"; /** * Prefix in SAMP_HUB value indicating web profile application name * ({@value}). */ public static final String WEBPROFILE_HUB_PREFIX = "web-appname:"; /** * Constructor with configuration options. * * @param securityMap map containing security information for registration * @param xClientFactory XML-RPC client factory * @param hubEndpoint XML-RPC endpoint for hub server */ public WebClientProfile( Map securityMap, SampXmlRpcClientFactory xClientFactory, URL hubEndpoint ) { securityMap_ = securityMap; xClientFactory_ = xClientFactory; hubEndpoint_ = hubEndpoint; } /** * Constructor with declared client name. * * @param appName client's declared application name * (samp.name entry in security-info map) */ public WebClientProfile( String appName ) { this( createSecurityMap( appName ), XmlRpcKit.getInstance().getClientFactory(), getDefaultHubEndpoint() ); } /** * Constructor with no arguments. The client's declared application * name will be as given by {@link #getDefaultAppName}. */ public WebClientProfile() { this( getDefaultAppName() ); } public boolean isHubRunning() { try { SampXmlRpcClient xClient = xClientFactory_.createClient( hubEndpoint_ ); xClient.callAndWait( WEBSAMP_HUB_PREFIX + "ping", new ArrayList() ); return true; } catch ( IOException e ) { return false; } } public HubConnection register() throws SampException { try { return new WebHubConnection( xClientFactory_ .createClient( hubEndpoint_ ), securityMap_ ); } catch ( SampException e ) { for ( Throwable ex = e; ex != null; ex = ex.getCause() ) { if ( ex instanceof ConnectException ) { return null; } } throw e; } catch ( ConnectException e ) { return null; } catch ( IOException e ) { throw new SampException( e ); } } /** * Returns the hub XML-RPC endpoint used by this profile. * * @return hub endpoint URL */ public URL getHubEndpoint() { return hubEndpoint_; } /** * Returns the hub XML-RPC endpoint defined by the Web Profile. * * @return Web Profile hub endpoint URL */ public static URL getDefaultHubEndpoint() { String surl = "http://" + SampUtils.getLocalhost() + ":" + WEBSAMP_PORT + WEBSAMP_PATH; try { return new URL( surl ); } catch ( MalformedURLException e ) { throw new AssertionError( "http scheme not supported?? " + surl ); } } /** * Returns a default instance of this profile. * * @return default web client profile instance */ public static WebClientProfile getInstance() { return new WebClientProfile(); } /** * Returns the default application name used by this profile if none * is supplied explicitly. * If the SAMP_HUB environment variable has the form * "web-appname:<appname>" it is taken from there; * otherwise it's something like "Unknown". * * @return default declared client name */ public static String getDefaultAppName() { String hubloc = Platform.getPlatform() .getEnv( DefaultClientProfile.HUBLOC_ENV ); return hubloc != null && hubloc.startsWith( WEBPROFILE_HUB_PREFIX ) ? hubloc.substring( WEBPROFILE_HUB_PREFIX.length() ) : "Unknown Application"; } /** * Constructs a security-info map suitable for presentation at * registration time, containing the mandatory samp.name entry. * * @param appName samp.name entry * @return security map */ private static Map createSecurityMap( String appName ) { Map map = new HashMap(); map.put( Metadata.NAME_KEY, appName ); return map; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebCredentialPresenter.java000066400000000000000000000111241356450004300272200ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Logger; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.HttpServer; /** * CredentialPresenter for use with the Web Profile. * *

Uses the following securityMap items: *

*
samp.name:
*
Self-declared client name. * Mandatory, but since it's supplied by the client, it doesn't tell * you anything trustworthy. *
*
* and the following HTTP headers: *
*
Origin:
*
Application origin, present if CORS is in use. * See Cross-Origin Resource Sharing, * The Web Origin Concept (RFC 6454). *
*
Referer:
*
Connection URL, present at whim of browser. * See HTTP/1.1 (RFC2616), sec 14.36. *
*
* *

The sole instance of this singleton class is {@link #INSTANCE}. * * @author Mark Taylor * @since 20 Jun 2016 */ public class WebCredentialPresenter implements CredentialPresenter { /** Singleton instance. */ public static final WebCredentialPresenter INSTANCE = new WebCredentialPresenter(); /** Origin header. */ public static final String ORIGIN_HDR = "Origin"; /** Referer header. */ public static final String REFERER_HDR = "Referer"; private static final Logger logger_ = Logger.getLogger( WebCredentialPresenter.class.getName() ); /** * Private sole constructor prevents external instantiation. */ private WebCredentialPresenter() { } public Presentation createPresentation( HttpServer.Request request, Map securityMap, AuthResourceBundle.Content authContent ) throws SampException { // Application self-declared name. Required, but doesn't prove much. String appName = ClientAuthorizers.getAppName( securityMap ); // Application origin (see http://www.w3.org/TR/cors/, // http://tools.ietf.org/html/draft-abarth-origin); // present if CORS is in use. Map headerMap = request.getHeaderMap(); String origin = HttpServer.getHeader( headerMap, ORIGIN_HDR ); // Referer header (RFC2616 sec 14.36) - present at whim of browser. String referer = HttpServer.getHeader( headerMap, REFERER_HDR ); final Map map = new LinkedHashMap(); map.put( authContent.nameWord(), appName ); map.put( authContent.originWord(), origin ); map.put( "URL", referer ); final Object[] lines; if ( referer != null && origin != null && ! origin.equals( getOrigin( referer ) ) ) { logger_.warning( "Origin/Referer header mismatch: " + "\"" + origin + "\" != " + "\"" + getOrigin( referer ) + "\"" ); lines = new Object[] { "WARNING: Origin/Referer header mismatch!", "WARNING: This looks suspicious.", }; } else { lines = new Object[ 0 ]; } return new Presentation() { public Map getAuthEntries() { return map; } public Object[] getAuthLines() { return lines; } }; } /** * Returns the serialized origin for a given URI string. * @see The Web Origin Concept * * @param uri URI * @return origin of uri, * null (note: not "null") if it cannot be determined */ private String getOrigin( String uri ) { if ( uri == null ) { return null; } URL url; try { url = new URL( uri ); } catch ( MalformedURLException e ) { return null; } String scheme = url.getProtocol(); String host = url.getHost(); int portnum = url.getPort(); StringBuffer sbuf = new StringBuffer() .append( scheme ) .append( "://" ) .append( host ); if ( portnum >= 0 && portnum != url.getDefaultPort() ) { sbuf.append( ":" ) .append( Integer.toString( portnum ) ); } return sbuf.toString().toLowerCase(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubActor.java000066400000000000000000000143061356450004300247720ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.List; import java.util.Map; import org.astrogrid.samp.client.SampException; /** * Defines the XML-RPC methods which must be implemented by a * Web Profile hub. * The register method is handled separately, since it has special * requirements as regards the HTTP request that it arrives on. * * @author Mark Taylor * @since 2 Feb 2011 */ interface WebHubActor { /** * Throws an exception if service is not operating. */ void ping() throws SampException; /** * Throws an exception if service is not operating. * * @param privateKey ignored */ void ping( String privateKey ) throws SampException; /** * Unregisters a registered client. * * @param privateKey calling client private key */ void unregister( String privateKey ) throws SampException; /** * Declares metadata for the calling client. * * @param privateKey calling client private key * @param meta {@link org.astrogrid.samp.Metadata}-like map */ void declareMetadata( String privateKey, Map meta ) throws SampException; /** * Returns metadata for a given client. * * @param privateKey calling client private key * @param clientId public ID for client whose metadata is required * @return {@link org.astrogrid.samp.Metadata}-like map */ Map getMetadata( String privateKey, String clientId ) throws SampException; /** * Declares subscription information for the calling client. * * @param privateKey calling client private key * @param subs {@link org.astrogrid.samp.Subscriptions}-like map */ void declareSubscriptions( String privateKey, Map subs ) throws SampException; /** * Returns subscriptions for a given client. * * @param privateKey calling client private key * @return {@link org.astrogrid.samp.Subscriptions}-like map */ Map getSubscriptions( String privateKey, String clientId ) throws SampException; /** * Returns a list of the public-ids of all currently registered clients. * * @param privateKey calling client private key * @return list of Strings */ List getRegisteredClients( String privateKey ) throws SampException; /** * Returns a map of the clients subscribed to a given MType. * * @param privateKey calling client private key * @param mtype MType of interest * @return map in which the keys are the public-ids of clients subscribed * to mtype */ Map getSubscribedClients( String privateKey, String mtype ) throws SampException; /** * Sends a message to a given client without wanting a response. * * @param privateKey calling client private key * @param recipientId public-id of client to receive message * @param msg {@link org.astrogrid.samp.Message}-like map */ void notify( String privateKey, String recipientId, Map msg ) throws SampException; /** * Sends a message to all subscribed clients without wanting a response. * * @param privateKey calling client private key * @param msg {@link org.astrogrid.samp.Message}-like map * @return list of public-ids for clients to which the notify will be sent */ List notifyAll( String privateKey, Map msg ) throws SampException; /** * Sends a message to a given client expecting a response. * * @param privateKey calling client private key * @param recipientId public-id of client to receive message * @param msgTag arbitrary string tagging this message for caller's * benefit * @param msg {@link org.astrogrid.samp.Message}-like map * @return message ID */ String call( String privateKey, String recipientId, String msgTag, Map msg ) throws SampException; /** * Sends a message to all subscribed clients expecting responses. * * @param privateKey calling client private key * @param msgTag arbitrary string tagging this message for caller's * benefit * @param msg {@link org.astrogrid.samp.Message}-like map * @return public-id->msg-id map for clients to which an attempt to * send the call will be made */ Map callAll( String privateKey, String msgTag, Map msg ) throws SampException; /** * Sends a message synchronously to a client. * * @param privateKey calling client private key * @param recipientId public-id of client to receive message * @param msg {@link org.astrogrid.samp.Message}-like map * @param timeout timeout in seconds encoded as a SAMP int * @return {@link org.astrogrid.samp.Response}-like map */ Map callAndWait( String privateKey, String recipientId, Map msg, String timeout ) throws SampException; /** * Responds to a previously sent message. * * @param privateKey calling client private key * @param msgId ID associated with earlier send * @param response {@link org.astrogrid.samp.Response}-like map */ void reply( String privateKey, String msgId, Map response ) throws SampException; /** * Indicates that the client will or will not be calling * {@link #pullCallbacks} to receive callable client-type * callbacks until further notice. * * @param privateKey calling client private key * @param allow flag indicating that the client will/will not * be pulling callbacks, encoded as a SAMP boolean ("1"/"0") */ void allowReverseCallbacks( String privateKey, String allow ) throws SampException; /** * Waits for up to a certain length of time for any callbacks to be * delivered. * * @param privateKey calling client private key * @param timeout timeout in seconds encoded as a SAMP int * @return list of {@link Callback}-like maps ready for * processing by the client; may be empty if none are ready */ List pullCallbacks( String privateKey, String timeout ) throws SampException; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubConnection.java000066400000000000000000000141611356450004300260200ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.xmlrpc.SampXmlRpcClient; import org.astrogrid.samp.xmlrpc.XmlRpcHubConnection; /** * HubConnection implementation for the Web Profile. * * @author Mark Taylor * @since 3 Feb 2011 */ class WebHubConnection extends XmlRpcHubConnection { private final String appName_; private final String clientKey_; private CallWorker callWorker_; private static Logger logger_ = Logger.getLogger( WebHubConnection.class.getName() ); /** * Constructor. * * @param xClient XML-RPC client * @param securityMap security information map * @param appName client's declared name */ public WebHubConnection( SampXmlRpcClient xClient, Map securityMap ) throws SampException { super( xClient, WebClientProfile.WEBSAMP_HUB_PREFIX, Collections.singletonList( securityMap ) ); Object nameObj = securityMap.get( Metadata.NAME_KEY ); appName_ = nameObj instanceof String ? (String) nameObj : "??"; clientKey_ = getRegInfo().getPrivateKey(); } public Object getClientKey() { return clientKey_; } public void setCallable( CallableClient client ) throws SampException { CallWorker oldWorker = callWorker_; callWorker_ = null; if ( oldWorker != null ) { oldWorker.stopped_ = true; } exec( "allowReverseCallbacks", new Object[] { SampUtils.encodeBoolean( client != null ) } ); if ( client != null ) { CallWorker callWorker = new CallWorker( this, client, appName_ ); callWorker.start(); callWorker_ = callWorker; } } /** * Thread that performs repeated long polls to pull callbacks from the * hub and passes them on to this connection's CallableClient for * execution. */ private static class CallWorker extends Thread { private final XmlRpcHubConnection xconn_; private final CallableClient client_; private final int timeoutSec_ = 60 * 10; private final long minWaitMillis_ = 5 * 1000; private volatile boolean stopped_; /** * Constructor. * * @param xconn hub connection * @parma client callable client * @param appName client's name */ CallWorker( XmlRpcHubConnection xconn, CallableClient client, String appName ) { super( "Web Profile Callback Puller for " + appName ); xconn_ = xconn; client_ = client; setDaemon( true ); } public void run() { String stimeout = SampUtils.encodeInt( timeoutSec_ ); while ( true && ! stopped_ ) { long start = System.currentTimeMillis(); Object result; try { result = xconn_.exec( "pullCallbacks", new Object[] { stimeout } ); } catch ( Exception e ) { long wait = System.currentTimeMillis() - start; if ( wait < minWaitMillis_ ) { seriousError( e ); } else { logger_.config( "pullCallbacks timeout? " + ( wait / 1000 ) + "s" ); } break; } catch ( Throwable e ) { seriousError( e ); break; } if ( ! stopped_ ) { if ( result instanceof List ) { List resultList = (List) result; for ( Iterator it = resultList.iterator(); it.hasNext(); ) { try { final Callback cb = new Callback( (Map) it.next() ); new Thread( "Web Profile Callback" ) { public void run() { try { ClientCallbackOperation .invoke( cb, client_ ); } catch ( Throwable e ) { logger_.log( Level.WARNING, "Callback failure: " + e.getMessage(), e ); } } }.start(); } catch ( Throwable e ) { logger_.log( Level.WARNING, e.getMessage(), e ); } } } else { logger_.warning( "pullCallbacks result " + "is not a List - ignore" ); } } } } /** * Invoked if there is a serious (non-timeout) error when polling * for callbacks. This currently stops the polling for good. * That may be a drastic response, but at least it prevents * repeated high-frequency polling attempts to a broken server, * which might otherwise result. * * @parm e error which caused the trouble */ private void seriousError( Throwable e ) { stopped_ = true; logger_.log( Level.WARNING, "Fatal pullCallbacks error - stopped listening", e ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubProfile.java000066400000000000000000000377121356450004300253300ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.io.IOException; import java.io.PrintStream; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.util.logging.Logger; import javax.swing.JToggleButton; import javax.swing.SwingUtilities; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.httpd.HttpServer; import org.astrogrid.samp.hub.ConfigHubProfile; import org.astrogrid.samp.hub.HubProfile; import org.astrogrid.samp.hub.KeyGenerator; import org.astrogrid.samp.hub.MessageRestriction; import org.astrogrid.samp.xmlrpc.internal.InternalServer; import org.astrogrid.samp.xmlrpc.internal.RpcLoggingInternalServer; import org.astrogrid.samp.xmlrpc.internal.XmlLoggingInternalServer; /** * HubProfile implementation for Web Profile. * * @author Mark Taylor * @author Laurent Bourges * @since 2 Feb 2011 */ public class WebHubProfile implements HubProfile, ConfigHubProfile { private final ServerFactory serverFactory_; private final ClientAuthorizer auth_; private final KeyGenerator keyGen_; private final ConfigEnabler configEnabler_; private final ConfigEnabler configDisabler_; private MessageRestriction mrestrict_; private boolean controlUrls_; private InternalServer xServer_; private JToggleButton.ToggleButtonModel[] configModels_; private static final Logger logger_ = Logger.getLogger( WebHubProfile.class.getName() ); /** * Constructs a profile with configuration options. * * @param serverFactory factory for server providing HTTP * and XML-RPC implementation * @param auth client authorizer implementation * @param mrestrict restriction for permitted outward MTypes * @param keyGen key generator for private keys * @param controlUrls true iff access to local URLs is to be restricted */ public WebHubProfile( ServerFactory serverFactory, ClientAuthorizer auth, MessageRestriction mrestrict, KeyGenerator keyGen, boolean controlUrls ) { serverFactory_ = serverFactory; auth_ = auth; mrestrict_ = mrestrict; keyGen_ = keyGen; controlUrls_ = controlUrls; /* These Runnables are set up here rather than being defined as * anonymous classes where they are used to work round an obscure * JNLP bug related to classloading and JVM shutdown. */ configEnabler_ = new ConfigEnabler( true ); configDisabler_ = new ConfigEnabler( false ); } /** * Constructs a profile with default configuration. */ public WebHubProfile() throws IOException { this( new ServerFactory(), new HubSwingClientAuthorizer( null, WebCredentialPresenter.INSTANCE ), ListMessageRestriction.DEFAULT, createKeyGenerator(), true ); } public String getProfileName() { return "Web"; } public MessageRestriction getMessageRestriction() { return mrestrict_; } public synchronized void start( ClientProfile profile ) throws IOException { if ( isRunning() ) { logger_.info( "Profile already running" ); return; } xServer_ = serverFactory_.createSampXmlRpcServer(); HttpServer hServer = xServer_.getHttpServer(); WebHubXmlRpcHandler wxHandler = new WebHubXmlRpcHandler( profile, auth_, keyGen_, hServer.getBaseUrl(), controlUrls_ ? new UrlTracker() : null ); logger_.info( "Web Profile URL controls: " + ( controlUrls_ ? "on" : "off" ) ); logger_.info( "Web Profile MType restrictions: " + mrestrict_ ); xServer_.addHandler( wxHandler ); hServer.addHandler( wxHandler.getUrlTranslationHandler() ); hServer.start(); if ( configModels_ != null ) { SwingUtilities.invokeLater( configDisabler_ ); } } public synchronized boolean isRunning() { return xServer_ != null; } public synchronized void stop() { if ( ! isRunning() ) { logger_.info( "Profile already stopped" ); return; } xServer_.getHttpServer().stop(); xServer_ = null; if ( configModels_ != null ) { SwingUtilities.invokeLater( configEnabler_ ); } } public synchronized JToggleButton.ToggleButtonModel[] getConfigModels() { if ( configModels_ == null ) { configModels_ = createConfigModels(); } return configModels_; } /** * Creates and returns some toggle models for configuration. * They are only enabled when the profile is not running. */ private JToggleButton.ToggleButtonModel[] createConfigModels() { ConfigModel[] models = new ConfigModel[] { new ConfigModel( "CORS cross-domain access" ) { void setOn( boolean on ) { serverFactory_ .setOriginAuthorizer( on ? OriginAuthorizers.TRUE : OriginAuthorizers.FALSE ); } boolean isOn() { return serverFactory_.getOriginAuthorizer().authorize( "" ); } }, new ConfigModel( "Flash cross-domain access" ) { void setOn( boolean on ) { serverFactory_.setAllowFlash( on ); } boolean isOn() { return serverFactory_.isAllowFlash(); } }, new ConfigModel( "Silverlight cross-domain access" ) { void setOn( boolean on ) { serverFactory_.setAllowSilverlight( on ); } boolean isOn() { return serverFactory_.isAllowSilverlight(); } }, new ConfigModel( "URL Controls" ) { void setOn( boolean on ) { controlUrls_ = on; } boolean isOn() { return controlUrls_; } }, new ConfigModel( "MType Restrictions" ) { void setOn( boolean on ) { mrestrict_ = on ? ListMessageRestriction.DEFAULT : null; } boolean isOn() { return mrestrict_ != null; } }, }; boolean enabled = ! isRunning(); for ( int i = 0; i < models.length; i++ ) { models[ i ].setEnabled( enabled ); } return models; } /** * Convenience method to return a new key generator * suitable for use with a WebHubProfile. * * @return new key generator for web hub private keys */ public static KeyGenerator createKeyGenerator() { return new KeyGenerator( "wk:", 24, KeyGenerator.createRandom() ); } /** * Runnable to be called on the Event Dispatch Thread which sets the * enabledness of the user controls for configuration of this profile. */ private class ConfigEnabler implements Runnable { private final boolean isEnabled_; /** * Constructor. * * @param isEnabled status assigned to config controls by calling * this object's run() method */ ConfigEnabler( boolean isEnabled ) { isEnabled_ = isEnabled; } public void run() { JToggleButton.ToggleButtonModel[] configModels = configModels_; if ( configModels != null ) { for ( int i = 0; i < configModels.length; i++ ) { configModels[ i ].setEnabled( isEnabled_ ); } } } } /** * Helper class to generate toggle button models for hub configuration. */ private static abstract class ConfigModel extends JToggleButton.ToggleButtonModel { private final String name_; /** * Constructor. * * @param name control name */ public ConfigModel( String name ) { name_ = name; } /** * Indicates whether this toggle is on. * * @return true iff selected */ abstract boolean isOn(); /** * Sets whether this toggle is on. * * @param on new selected value */ abstract void setOn( boolean on ); public boolean isSelected() { return isOn(); } public void setSelected( boolean on ) { setOn( on ); super.setSelected( on ); } public String toString() { return name_; } } /** * Creates and configures the HTTP server on which the Web Profile resides. */ public static class ServerFactory { private String logType_; private int port_; private String xmlrpcPath_; private boolean allowFlash_; private boolean allowSilverlight_; private OriginAuthorizer oAuth_; /** * Constructs a ServerFactory with default properties. */ public ServerFactory() { logType_ = null; port_ = WebClientProfile.WEBSAMP_PORT; xmlrpcPath_ = WebClientProfile.WEBSAMP_PATH; allowFlash_ = true; allowSilverlight_ = false; oAuth_ = OriginAuthorizers.TRUE; } /** * Returns a new internal server. * * @return new server for use with WebHubProfile */ public InternalServer createSampXmlRpcServer() throws IOException { String path = getXmlrpcPath(); ServerSocket socket = createServerSocket( getPort() ); String logType = getLogType(); OriginAuthorizer oAuth = getOriginAuthorizer(); PrintStream logOut = System.err; CorsHttpServer hServer = "http".equals( logType ) ? new LoggingCorsHttpServer( socket, oAuth, logOut ) : new CorsHttpServer( socket, oAuth ); if ( isAllowFlash() ) { hServer.addHandler( OpenPolicyResourceHandler .createFlashPolicyHandler( oAuth ) ); logger_.info( "Web Profile HTTP server permits " + "Flash-style cross-domain access" ); } else { logger_.info( "Web Profile HTTP server does not permit " + "Flash-style cross-domain access" ); } if ( isAllowSilverlight() ) { hServer.addHandler( OpenPolicyResourceHandler .createSilverlightPolicyHandler( oAuth ) ); logger_.info( "Web Profile HTTP server permits " + "Silverlight-style cross-domain access" ); } else { logger_.info( "Web Profile HTTP server does not permit " + "Silverlight-style cross-domain access" ); } hServer.setDaemon( true ); if ( "rpc".equals( logType ) ) { return new RpcLoggingInternalServer( hServer, path, logOut ); } else if ( "xml".equals( logType ) ) { return new XmlLoggingInternalServer( hServer, path, logOut ); } else if ( "none".equals( logType ) || "http".equals( logType ) || logType == null || logType.length() == 0 ) { return new InternalServer( hServer, path ); } else { throw new IllegalArgumentException( "Unknown logType " + logType ); } } /** * Sets the type of logging to use. * * @param logType logging type; * may be "http", "rpc", "xml", "none" or null */ public void setLogType( String logType ) { if ( logType == null || logType.equals( "http" ) || logType.equals( "rpc" ) || logType.equals( "xml" ) || logType.equals( "none" ) ) { logType_ = logType; } else { throw new IllegalArgumentException( "Unknown log type " + logType ); } } /** * Returns the type of logging to use. * * @return logging type; may be "http", "rpc", "xml", "none" or null */ public String getLogType() { return logType_; } /** * Sets the port number the server will run on. * If port=0, then an unused port will be used at run time. * * @param port port number */ public void setPort( int port ) { port_ = port; } /** * Returns the port number the server will run on. * * @return port number */ public int getPort() { return port_; } /** * Sets the path on the HTTP server at which the XML-RPC server * will reside. * * @param xmlrpcPath server path for XML-RPC server */ public void setXmlrpcPath( String xmlrpcPath ) { xmlrpcPath_ = xmlrpcPath; } /** * Returns the path on the HTTP server at which the XML-RPC server * will reside. * * @return XML-RPC path on server */ public String getXmlrpcPath() { return xmlrpcPath_; } /** * Sets whether Adobe Flash cross-domain workaround will be supported. * * @param allowFlash true iff supported */ public void setAllowFlash( boolean allowFlash ) { allowFlash_ = allowFlash; } /** * Indicates whether Adobe Flash cross-domain workaround * will be supported. * * @return true iff supported */ public boolean isAllowFlash() { return allowFlash_; } /** * Sets whether Microsoft Silverlight cross-domain workaround * will be supported. * * @param allowSilverlight true iff supported */ public void setAllowSilverlight( boolean allowSilverlight ) { allowSilverlight_ = allowSilverlight; } /** * Indicates whether Microsoft Silverlight cross-domain workaround * will be supported. * * @return true iff supported */ public boolean isAllowSilverlight() { return allowSilverlight_; } /** * Sets the authorization policy for external origins. * * @param oAuth authorizer */ public void setOriginAuthorizer( OriginAuthorizer oAuth ) { oAuth_ = oAuth; } /** * Returns the authorization policy for external origins. * * @return authorizer */ public OriginAuthorizer getOriginAuthorizer() { return oAuth_; } /** * Creates a socket on a given port to be used by the server this * object produces. * * @param port port number * @return new server socket */ protected ServerSocket createServerSocket( int port ) throws IOException { ServerSocket sock = new ServerSocket(); sock.setReuseAddress( true ); sock.bind( new InetSocketAddress( port ) ); return sock; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubProfileFactory.java000066400000000000000000000147101356450004300266510ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.logging.Level; import org.astrogrid.samp.hub.HubProfile; import org.astrogrid.samp.hub.HubProfileFactory; import org.astrogrid.samp.hub.KeyGenerator; import org.astrogrid.samp.hub.MessageRestriction; import org.astrogrid.samp.xmlrpc.internal.InternalServer; /** * HubProfileFactory implementation for Web Profile. * * @author Mark Taylor * @since 2 Feb 2011 */ public class WebHubProfileFactory implements HubProfileFactory { private static final String logUsage_ = "[-web:log none|http|xml|rpc]"; private static final String authUsage_ = "[-web:auth swing|true|false|extreme]"; private static final String corsUsage_ = "[-web:[no]cors]"; private static final String flashUsage_ = "[-web:[no]flash]"; private static final String silverlightUsage_ = "[-web:[no]silverlight]"; private static final String urlcontrolUsage_ = "[-web:[no]urlcontrol]"; private static final String restrictMtypeUsage_ = "[-web:[no]restrictmtypes]"; /** * Returns "web". */ public String getName() { return "web"; } public String[] getFlagsUsage() { return new String[] { logUsage_, authUsage_, corsUsage_, flashUsage_, silverlightUsage_, urlcontrolUsage_, restrictMtypeUsage_, }; } public HubProfile createHubProfile( List flagList ) throws IOException { // Process flags. String logType = "none"; String authType = "swing"; boolean useCors = true; boolean useFlash = true; boolean useSilverlight = false; boolean urlControl = true; boolean restrictMtypes = true; for ( Iterator it = flagList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.equals( "-web:log" ) ) { it.remove(); if ( it.hasNext() ) { logType = (String) it.next(); it.remove(); } else { throw new IllegalArgumentException( "Usage: " + logUsage_ ); } } else if ( arg.equals( "-web:auth" ) ) { it.remove(); if ( it.hasNext() ) { authType = (String) it.next(); it.remove(); } else { throw new IllegalArgumentException( "Usage: " + authUsage_ ); } } else if ( arg.equals( "-web:cors" ) ) { it.remove(); useCors = true; } else if ( arg.equals( "-web:nocors" ) ) { it.remove(); useCors = false; } else if ( arg.equals( "-web:flash" ) ) { it.remove(); useFlash = true; } else if ( arg.equals( "-web:noflash" ) ) { it.remove(); useFlash = false; } else if ( arg.equals( "-web:silverlight" ) ) { it.remove(); useSilverlight = true; } else if ( arg.equals( "-web:nosilverlight" ) ) { it.remove(); useSilverlight = false; } else if ( arg.equals( "-web:urlcontrol" ) ) { it.remove(); urlControl = true; } else if ( arg.equals( "-web:nourlcontrol" ) ) { it.remove(); urlControl = false; } else if ( arg.equals( "-web:restrictmtypes" ) ) { it.remove(); restrictMtypes = true; } else if ( arg.equals( "-web:norestrictmtypes" ) ) { it.remove(); restrictMtypes = false; } } // Prepare HTTP server. WebHubProfile.ServerFactory sfact = new WebHubProfile.ServerFactory(); try { sfact.setLogType( logType ); } catch ( IllegalArgumentException e ) { throw (IllegalArgumentException) new IllegalArgumentException( "Unknown log type " + logType + "; Usage: " + logUsage_ ) .initCause( e ); } sfact.setOriginAuthorizer( useCors ? OriginAuthorizers.TRUE : OriginAuthorizers.FALSE ); sfact.setAllowFlash( useFlash ); sfact.setAllowSilverlight( useSilverlight ); // Prepare client authorizer. final ClientAuthorizer clientAuth; if ( "swing".equalsIgnoreCase( authType ) ) { clientAuth = ClientAuthorizers .createLoggingClientAuthorizer( new HubSwingClientAuthorizer( null, WebCredentialPresenter .INSTANCE ), Level.INFO, Level.INFO ); } else if ( "extreme".equalsIgnoreCase( authType ) ) { clientAuth = ClientAuthorizers .createLoggingClientAuthorizer( new ExtremeSwingClientAuthorizer( null ), Level.WARNING, Level.INFO ); } else if ( "true".equalsIgnoreCase( authType ) ) { clientAuth = ClientAuthorizers.TRUE; } else if ( "false".equalsIgnoreCase( authType ) ) { clientAuth = ClientAuthorizers.FALSE; } else { throw new IllegalArgumentException( "Unknown authorizer type " + authType + "; Usage: " + authUsage_ ); } // Prepare subscriptions mask. MessageRestriction mrestrict = restrictMtypes ? ListMessageRestriction.DEFAULT : null; // Construct and return an appropriately configured hub profile. return new WebHubProfile( sfact, clientAuth, mrestrict, WebHubProfile.createKeyGenerator(), urlControl ); } public Class getHubProfileClass() { return WebHubProfile.class; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/WebHubXmlRpcHandler.java000066400000000000000000000423071356450004300264270ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.HttpServer; import org.astrogrid.samp.httpd.URLMapperHandler; import org.astrogrid.samp.hub.KeyGenerator; import org.astrogrid.samp.xmlrpc.ActorHandler; /** * SampXmlRpcHandler implementation which passes Web Profile-type XML-RPC calls * to a hub connection factory to provide a Web Profile hub server. * * @author Mark Taylor * @since 2 Feb 2011 */ public class WebHubXmlRpcHandler extends ActorHandler { private final WebHubActorImpl impl_; private static final Logger logger_ = Logger.getLogger( WebHubXmlRpcHandler.class.getName() ); /** * Constructor. * * @param profile hub connection factory * @param auth client authorizer * @param keyGen key generator for private keys * @param baseUrl base URL of HTTP server, used for URL translation * @param urlTracker tracks URLs in messages to restrict use in URL * translation service for security reasons; may be null for * no restrictions */ public WebHubXmlRpcHandler( ClientProfile profile, ClientAuthorizer auth, KeyGenerator keyGen, URL baseUrl, UrlTracker urlTracker ) { super( WebClientProfile.WEBSAMP_HUB_PREFIX, WebHubActor.class, new WebHubActorImpl( profile, auth, keyGen, baseUrl, urlTracker ) ); impl_ = (WebHubActorImpl) getActor(); } public Object handleCall( String fqName, List params, Object reqObj ) throws Exception { String regMethod = WebClientProfile.WEBSAMP_HUB_PREFIX + "register"; if ( regMethod.equals( fqName ) && reqObj instanceof HttpServer.Request ) { HttpServer.Request req = (HttpServer.Request) reqObj; final Map securityMap; if ( params.size() == 1 && params.get( 0 ) instanceof Map ) { securityMap = (Map) params.get( 0 ); } else if ( params.size() == 1 && params.get( 0 ) instanceof String ) { securityMap = new HashMap(); securityMap.put( Metadata.NAME_KEY, (String) params.get( 0 ) ); logger_.info( "Deprecated register call signature " + "(arg is string appName not map security-info)" ); } else { throw new IllegalArgumentException( "Bad args for " + regMethod + "(map)" ); } Map result = impl_.register( req, securityMap ); assert result != null; return result; } else { return super.handleCall( fqName, params, reqObj ); } } /** * Returns a handler suitable for performing URL translations on behalf * of sandboxed clients as required by the Web Profile. * * @return url translation handler */ public HttpServer.Handler getUrlTranslationHandler() { return impl_.getUrlTranslationHandler(); } protected Object invokeMethod( Method method, Object obj, Object[] args ) throws IllegalAccessException, InvocationTargetException { return method.invoke( obj, args ); } /** * WebHubActor implementation. */ private static class WebHubActorImpl implements WebHubActor { private final ClientProfile profile_; private final ClientAuthorizer auth_; private final KeyGenerator keyGen_; private final Map regMap_; private final URLTranslationHandler urlTranslator_; private final URL baseUrl_; private final UrlTracker urlTracker_; /** * Constructor. * * @param profile hub connection factory * @param auth client authorizer * @param keyGen key generator for private keys * @param baseUrl HTTP server base URL * @param urlTracker controls access to translated URLs, * may be null for no control */ public WebHubActorImpl( ClientProfile profile, ClientAuthorizer auth, KeyGenerator keyGen, URL baseUrl, UrlTracker urlTracker ) { profile_ = profile; auth_ = auth; keyGen_ = keyGen; baseUrl_ = baseUrl; urlTracker_ = urlTracker; regMap_ = Collections.synchronizedMap( new HashMap() ); urlTranslator_ = new URLTranslationHandler( "/proxied/", regMap_.keySet(), urlTracker ); } /** * Returns a handler suitable for performing URL translations on behalf * of sandboxed clients as required by the Web Profile. * * @return url translation handler */ public HttpServer.Handler getUrlTranslationHandler() { return urlTranslator_; } /** * Attempt client registration. An exception is thrown if registration * fails for any reason. * * @param request HTTP request from applicant * @param securityMap map of required security information * supplied by applicant * @return registration information if registration is successful */ public RegInfo register( HttpServer.Request request, Map securityMap ) throws SampException { if ( profile_.isHubRunning() ) { auth_.authorize( request, securityMap ); HubConnection connection = profile_.register(); if ( connection != null ) { if ( urlTracker_ != null ) { connection = new UrlTrackerHubConnection( connection, urlTracker_ ); } String clientKey = keyGen_.next(); regMap_.put( clientKey, new Registration( connection ) ); String urlTrans = baseUrl_ + urlTranslator_ .getTranslationBasePath( clientKey ); RegInfo regInfo = new RegInfo( connection.getRegInfo() ); regInfo.put( RegInfo.PRIVATEKEY_KEY, clientKey ); regInfo.put( WebClientProfile.URLTRANS_KEY, urlTrans ); return regInfo; } else { throw new SampException( "Hub is not running" ); } } else { throw new SampException( "Hub not running" ); } } public void unregister( String clientKey ) throws SampException { HubConnection connection = getConnection( clientKey ); regMap_.remove( clientKey ); connection.unregister(); } public void allowReverseCallbacks( String clientKey, String allow ) throws SampException { boolean isAllowed = SampUtils.decodeBoolean( allow ); Registration reg = getRegistration( clientKey ); synchronized ( reg ) { if ( isAllowed == ( reg.callable_ != null ) ) { return; } else if ( isAllowed ) { WebCallableClient callable = new WebCallableClient(); reg.connection_.setCallable( callable ); reg.callable_ = callable; } else { reg.connection_.setCallable( null ); reg.callable_.endCallbacks(); reg.callable_ = null; } assert isAllowed == ( reg.callable_ != null ); } } public List pullCallbacks( String clientKey, String timeout ) throws SampException { WebCallableClient callable = getRegistration( clientKey ).callable_; if ( callable != null ) { return callable .pullCallbacks( SampUtils.decodeInt( timeout ) ); } else { throw new SampException( "Client is not callable (first invoke" + " allowReverseCallbacks)" ); } } public void declareMetadata( String clientKey, Map meta ) throws SampException { getConnection( clientKey ).declareMetadata( meta ); } public Map getMetadata( String clientKey, String clientId ) throws SampException { return getConnection( clientKey ).getMetadata( clientId ); } public void declareSubscriptions( String clientKey, Map subs ) throws SampException { getRegistration( clientKey ).subs_ = new Subscriptions( subs == null ? new HashMap() : subs ); getConnection( clientKey ).declareSubscriptions( subs ); } public Map getSubscriptions( String clientKey, String clientId ) throws SampException { return getConnection( clientKey ).getSubscriptions( clientId ); } public List getRegisteredClients( String clientKey ) throws SampException { return Arrays.asList( getConnection( clientKey ) .getRegisteredClients() ); } public Map getSubscribedClients( String clientKey, String mtype ) throws SampException { return getConnection( clientKey ).getSubscribedClients( mtype ); } public void notify( String clientKey, String recipientId, Map msg ) throws SampException { getConnection( clientKey ).notify( recipientId, msg ); } public List notifyAll( String clientKey, Map msg ) throws SampException { return getConnection( clientKey ).notifyAll( msg ); } public String call( String clientKey, String recipientId, String msgTag, Map msg ) throws SampException { return getConnection( clientKey ).call( recipientId, msgTag, msg ); } public Map callAll( String clientKey, String msgTag, Map msg ) throws SampException { return getConnection( clientKey ).callAll( msgTag, msg ); } public Map callAndWait( String clientKey, String recipientId, Map msg, String timeout ) throws SampException { return getConnection( clientKey ) .callAndWait( recipientId, msg, SampUtils.decodeInt( timeout ) ); } public void reply( String clientKey, String msgId, Map response ) throws SampException { getConnection( clientKey ).reply( msgId, response ); } public void ping() { if ( ! profile_.isHubRunning() ) { throw new RuntimeException( "No hub running" ); } } public void ping( String clientKey ) { ping(); } /** * Returns the registration object associated with a given private key. * * @param privateKey private key string known by client and hub * to identify the connection * @return registration object for client with key * privateKey * @throws SampException if no client is known with that private key */ private Registration getRegistration( String privateKey ) throws SampException { Registration reg = (Registration) regMap_.get( privateKey ); if ( reg == null ) { throw new SampException( "Unknown client key" ); } else { return reg; } } /** * Returns the connection object associated with a given private key. * * @param privateKey private key string known by client and hub * to identify the connection * @return connection object for client with key * privateKey * @throws SampException if no client is known with that private key */ private HubConnection getConnection( String privateKey ) throws SampException { return getRegistration( privateKey ).connection_; } } /** * HTTP handler which provides URL translation services for sandboxed * clients. */ private static class URLTranslationHandler implements HttpServer.Handler { private final String basePath_; private final Set keySet_; private final UrlTracker urlTracker_; /** * Constructor. * * @param basePath base path for HTTP server * @param keySet set of strings which contains keys for all * currently registered clients * @param urlTracker controls access to translated URLs, * may be null for no control */ public URLTranslationHandler( String basePath, Set keySet, UrlTracker urlTracker ) { if ( ! basePath.startsWith( "/" ) ) { basePath = "/" + basePath; } if ( ! basePath.endsWith( "/" ) ) { basePath = basePath + "/"; } basePath_ = basePath; keySet_ = keySet; urlTracker_ = urlTracker; } /** * Returns the translation base path that can be used by a client * with a given private key. * * @param privateKey client private key * @return URL translation base path that can be used by a * registered client with the given private key */ public String getTranslationBasePath( String privateKey ) { return basePath_ + privateKey + "?"; } public HttpServer.Response serveRequest( HttpServer.Request request ) { // Ignore requests outside this handler's domain. String path = request.getUrl(); if ( ! path.startsWith( basePath_ ) ) { return null; } // Ensure the URL has a query part. String relPath = path.substring( basePath_.length() ); int qIndex = relPath.indexOf( '?' ); if ( qIndex < 0 ) { return HttpServer.createErrorResponse( 404, "Not Found" ); } // Ensure a valid key for authorization is present; this makes // sure that only registered clients can use this service. String authKey = relPath.substring( 0, qIndex ); if ( ! keySet_.contains( authKey ) ) { return HttpServer.createErrorResponse( 403, "Forbidden" ); } // Extract the URL whose translation is being requested. assert path.substring( 0, path.indexOf( '?' ) + 1 ) .equals( getTranslationBasePath( authKey ) ); String targetString; try { targetString = SampUtils.uriDecode( relPath.substring( qIndex + 1 ) ); } catch ( RuntimeException e ) { return HttpServer.createErrorResponse( 400, "Bad Request" ); } URL targetUrl; try { targetUrl = new URL( targetString ); } catch ( MalformedURLException e ) { return HttpServer.createErrorResponse( 400, "Bad Request" ); } // Check permissions. if ( urlTracker_ != null && ! urlTracker_.isUrlPermitted( targetUrl ) ) { return HttpServer.createErrorResponse( 403, "Forbidden" ); } // Perform the translation and return the result. return URLMapperHandler.mapUrlResponse( request.getMethod(), targetUrl ); } } /** * Utility class to aggregate information about a registered client. */ private static class Registration { final HubConnection connection_; WebCallableClient callable_; Subscriptions subs_; /** * Constructor. * * @param connection hub connection */ Registration( HubConnection connection ) { connection_ = connection; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/web/package.html000066400000000000000000000000711356450004300242350ustar00rootroot00000000000000 Classes relating to the SAMP Web Profile. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/000077500000000000000000000000001356450004300225065ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/ActorHandler.java000066400000000000000000000233041356450004300257210ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.astrogrid.samp.DataException; /** * Utility class to facilitate constructing a SampXmlRpcHandler which handles * particular named methods. * You supply at construction time an interface which defines the methods * to be handled and an object which implements that interface. * This object then uses reflection to invoke the correct methods on the * implementation object as they are required from incoming XML-RPC * execute requests. This insulates the implementation object * from having to worry about any XML-RPC specifics. * * @author Mark Taylor * @since 15 Jul 2008 */ public abstract class ActorHandler implements SampXmlRpcHandler { private final String prefix_; private final Object actor_; private final Map methodMap_; private final Logger logger_ = Logger.getLogger( ActorHandler.class.getName() ); /** * Constructor. * * @param prefix string prepended to every method name in the * actorType interface to form the XML-RPC * methodName element * @param actorType interface defining the XML-RPC methods * @param actor object implementing actorType */ public ActorHandler( String prefix, Class actorType, Object actor ) { prefix_ = prefix; actor_ = actor; methodMap_ = new HashMap(); // Construct a map keyed by method signature of the known methods. Method[] methods = actorType.getDeclaredMethods(); for ( int im = 0; im < methods.length; im++ ) { Method method = methods[ im ]; if ( Modifier.isPublic( method.getModifiers() ) ) { String name = method.getName(); Class[] clazzes = method.getParameterTypes(); SampType[] types = new SampType[ clazzes.length ]; for ( int ic = 0; ic < clazzes.length; ic++ ) { types[ ic ] = SampType.getClassType( clazzes[ ic ] ); } Signature sig = new Signature( prefix_ + name, types ); methodMap_.put( sig, method ); } } } public boolean canHandleCall( String fqName ) { return fqName.startsWith( prefix_ ); } public Object handleCall( String fqName, List params, Object reqInfo ) throws Exception { if ( ! canHandleCall( fqName ) ) { throw new IllegalArgumentException( "No I can't" ); } // Work out the signature for this method and see if it is recognised. String name = fqName.substring( prefix_.length() ); List typeList = new ArrayList(); for ( Iterator it = params.iterator(); it.hasNext(); ) { typeList.add( SampType.getParamType( it.next() ) ); } SampType[] types = (SampType[]) typeList.toArray( new SampType[ 0 ] ); Signature sig = new Signature( fqName, types ); Method method = (Method) methodMap_.get( sig ); // If the signature is recognised, invoke the relevant method // on the implementation object. if ( method != null ) { Object result; Throwable error; try { result = invokeMethod( method, actor_, params.toArray() ); } catch ( InvocationTargetException e ) { Throwable e2 = e.getCause(); if ( e2 instanceof Error ) { throw (Error) e2; } else { throw (Exception) e2; } } catch ( Exception e ) { throw e; } catch ( Error e ) { throw e; } return result == null ? "" : result; } // If the signature is not recognised, but the method name is, // try to make a helpful comment. else { for ( Iterator it = methodMap_.keySet().iterator(); it.hasNext(); ) { Signature foundSig = (Signature) it.next(); if ( foundSig.name_.equals( fqName ) ) { throw new IllegalArgumentException( "Bad arguments: " + foundSig + " got " + sig.typeList_ ); } } throw new UnsupportedOperationException( "Unknown method " + fqName ); } } /** * Returns the implementation object for this handler. * * @return implementation object */ public Object getActor() { return actor_; } /** * Invokes a method reflectively on an object. * This method should be implemented in the obvious way, that is * return method.invoke(obj,params). * *

If the implementation is effectively prescribed, why is this * abstract method here? It's tricky. * The reason is so that reflective method invocation from this class * is done by code within the actor implementation class itself * rather than by code in the superclass, ActorHandler. * That in turn means that the actorType class specified * in the constructor does not need to be visible from * ActorHandler's package, only from the package where * the implementation class lives. * * @param method method to invoke * @param obj object to invoke the method on * @param args arguments for the method call * @see java.lang.reflect.Method#invoke */ protected abstract Object invokeMethod( Method method, Object obj, Object[] args ) throws IllegalAccessException, InvocationTargetException; /** * Enumeration of permitted types within a SAMP data structure. */ private static class SampType { /** String type. */ public static final SampType STRING = new SampType( String.class, "string" ); /** List type. */ public static final SampType LIST = new SampType( List.class, "list" ); /** Map type. */ public static final SampType MAP = new SampType( Map.class, "map" ); private final Class clazz_; private final String name_; /** * Constructor. * * @param clazz java class * @param name name of SAMP type */ private SampType( Class clazz, String name ) { clazz_ = clazz; name_ = name; } /** * Returns the java class corresponding to this type. * * @return class */ public Class getTypeClass() { return clazz_; } /** * Returns the SAMP name for this type. * * @return name */ public String toString() { return name_; } /** * Returns the SampType corresponding to a given java class. * * @param clazz class * @return SAMP type */ public static SampType getClassType( Class clazz ) { if ( String.class.equals( clazz ) ) { return STRING; } else if ( List.class.equals( clazz ) ) { return LIST; } else if ( Map.class.equals( clazz ) ) { return MAP; } else { throw new IllegalArgumentException( "Illegal type " + clazz.getName() ); } } /** * Returns the SampType corresponding to a given object. * * @param param object * return SAMP type */ public static SampType getParamType( Object param ) { if ( param instanceof String ) { return STRING; } else if ( param instanceof List ) { return LIST; } else if ( param instanceof Map ) { return MAP; } else { throw new DataException( "Param is not a SAMP type" ); } } } /** * Characterises a method signature. * The equals and hashCode methods are * implemented sensibly. */ private static class Signature { private final String name_; private final List typeList_; /** * Constructor. * * @param name method name * @param types types of method arguments */ Signature( String name, SampType[] types ) { name_ = name; typeList_ = new ArrayList( Arrays.asList( types ) ); } public boolean equals( Object o ) { if ( o instanceof Signature ) { Signature other = (Signature) o; return this.name_.equals( other.name_ ) && this.typeList_.equals( other.typeList_ ); } else { return false; } } public int hashCode() { int code = 999; code = 23 * code + name_.hashCode(); code = 23 * code + typeList_.hashCode(); return code; } public String toString() { return name_ + typeList_; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/CallableClientServer.java000066400000000000000000000104041356450004300273750ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.IOException; import java.net.URL; import java.util.Map; import java.util.HashMap; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; /** * XML-RPC server which can host {@link CallableClient} instances. * There should usually be only one instance of this class for each * SampXmlRpcServer - see {@link #getInstance}. * * @author Mark Taylor * @since 16 Jul 2008 */ class CallableClientServer { private final URL url_; private SampXmlRpcServer server_; private ClientXmlRpcHandler clientHandler_; private static final Map serverMap_ = new HashMap(); /** * Constructor. Note that a {@link #getInstance} method exists as well. * * @param server XML-RPC server hosting this client server */ public CallableClientServer( SampXmlRpcServer server ) throws IOException { server_ = server; clientHandler_ = new ClientXmlRpcHandler(); server_.addHandler( clientHandler_ ); url_ = server_.getEndpoint(); } /** * Returns the XML-RPC endpoint for this server. * * @return endpoint url */ public URL getUrl() { return url_; } /** * Adds a CallableClient object to this server. * * @param connection hub connection for the registered client on behalf * of which the client will operate * @param callable callable client object */ public void addClient( HubConnection connection, CallableClient callable ) { if ( clientHandler_ == null ) { throw new IllegalStateException( "Closed" ); } clientHandler_.addClient( connection, callable ); } /** * Removes a CallableClient object from this server. * * @param privateKey hub connection for which this client was added */ public void removeClient( HubConnection connection ) { if ( clientHandler_ != null ) { clientHandler_.removeClient( connection ); } } /** * Tidies up resources. Following a call to this method, no further * clients can be added. */ public void close() { if ( server_ != null ) { server_.removeHandler( clientHandler_ ); } server_ = null; clientHandler_ = null; } /** * Indicates whether this server currently has any clients. * * @return true iff there are clients */ boolean hasClients() { return clientHandler_ != null && clientHandler_.getClientCount() > 0; } /** * Returns an instance of CallableClientServer for use with a given * XML-RPC server. Because of the implementation, only one * CallableClientServer is permitted per XML-RPC server, so if one * has already been installed for the given server, * that one will be returned. Otherwise a new one will be constructed, * installed and returned. * *

To prevent memory leaks, once any clients added to the returned * server have been removed (the client count drops to zero), the * server will be closed and cannot be re-used. * * @param server XML-RPC server * @return new or re-used CallableClientServer which is installed on * server */ public static synchronized CallableClientServer getInstance( SampXmlRpcServerFactory serverFact ) throws IOException { final SampXmlRpcServer server = serverFact.getServer(); if ( ! serverMap_.containsKey( server ) ) { CallableClientServer clientServer = new CallableClientServer( server ) { public void removeClient( HubConnection connection ) { super.removeClient( connection ); if ( ! hasClients() ) { close(); synchronized ( CallableClientServer.class ) { serverMap_.remove( server ); } } } }; serverMap_.put( server, clientServer ); } return (CallableClientServer) serverMap_.get( server ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/ClientActor.java000066400000000000000000000030771356450004300255670ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.util.Map; /** * Defines the methods which an XML-RPC callable client must implement. * * @author Mark Taylor * @since 16 Jul 2008 */ interface ClientActor { /** * Receives a message for which no response is required. * * @param privateKey private key for hub-client communication * @param senderId public ID of sending client * @param msg message */ void receiveNotification( String privateKey, String senderId, Map msg ) throws Exception; /** * Receives a message for which a response is required. * The implementation must take care to call the hub's reply * method at some future point. * * @param privateKey private key for hub-client communication * @param senderId public ID of sending client * @param msgId message identifier for later use with reply * @param msg message */ void receiveCall( String privateKey, String senderId, String msgId, Map msg ) throws Exception; /** * Receives a response to a message previously sent by this client. * * @param privateKey private key for hub-client communication * @param responderId public ID of responding client * @param msgTag client-defined tag labelling previously-sent message * @param response returned response object */ void receiveResponse( String privateKey, String responderId, String msgTag, Map response ) throws Exception; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/ClientXmlRpcHandler.java000066400000000000000000000152251356450004300272200ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.ErrInfo; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; /** * SampXmlRpcHandler implementation which passes Standard Profile-like XML-RPC * calls to one or more {@link CallableClient}s to provide client callbacks * from the hub. * * @author Mark Taylor * @since 16 Jul 2008 */ class ClientXmlRpcHandler extends ActorHandler { private final ClientActorImpl clientActor_; private static final Logger logger_ = Logger.getLogger( ClientXmlRpcHandler.class.getName() ); /** * Constructor. */ public ClientXmlRpcHandler() { super( "samp.client.", ClientActor.class, new ClientActorImpl() ); clientActor_ = (ClientActorImpl) getActor(); } /** * Adds a CallableClient object to this server. * * @param connection hub connection for the registered client on behalf * of which the client will operate * @param callable callable client object */ public void addClient( HubConnection connection, CallableClient callable ) { clientActor_.entryMap_.put( connection.getRegInfo().getPrivateKey(), new Entry( connection, callable ) ); } /** * Removes a CallableClient object from this server. * * @param privateKey hub connection for which this client was added */ public void removeClient( HubConnection connection ) { clientActor_.entryMap_ .remove( connection.getRegInfo().getPrivateKey() ); } /** * Returns the number of clients currently owned by this handler. * * @return client count */ public int getClientCount() { return clientActor_.entryMap_.size(); } protected Object invokeMethod( Method method, Object obj, Object[] args ) throws IllegalAccessException, InvocationTargetException { return method.invoke( obj, args ); } /** * Implementation of the {@link ClientActor} interface which does the * work for this class. * The correct CallableClient is determined from the private key, * and the work is then delegated to it. */ private static class ClientActorImpl implements ClientActor { private Map entryMap_ = new HashMap(); // String -> Entry public void receiveNotification( String privateKey, final String senderId, Map msg ) { Entry entry = getEntry( privateKey ); final CallableClient callable = entry.callable_; final Message message = Message.asMessage( msg ); final String label = "Notify " + senderId + " " + message.getMType(); new Thread( label ) { public void run() { try { callable.receiveNotification( senderId, message ); } catch ( Throwable e ) { logger_.log( Level.INFO, label + " error", e ); } } }.start(); } public void receiveCall( String privateKey, final String senderId, final String msgId, Map msg ) throws Exception { Entry entry = getEntry( privateKey ); final CallableClient callable = entry.callable_; final HubConnection connection = entry.connection_; final Message message = Message.asMessage( msg ); final String label = "Call " + senderId + " " + message.getMType(); new Thread( label ) { public void run() { try { callable.receiveCall( senderId, msgId, message ); } catch ( Throwable e ) { try { Response response = Response .createErrorResponse( new ErrInfo( e ) ); connection.reply( msgId, response ); } catch ( Throwable e2 ) { logger_.log( Level.INFO, label + " error replying", e2 ); } } } }.start(); } public void receiveResponse( String privateKey, final String responderId, final String msgTag, Map resp ) throws Exception { Entry entry = getEntry( privateKey ); final CallableClient callable = entry.callable_; final Response response = Response.asResponse( resp ); final String label = "Reply " + responderId; new Thread( label ) { public void run() { try { callable.receiveResponse( responderId, msgTag, response ); } catch ( Throwable e ) { logger_.log( Level.INFO, label + " error replying", e ); } } }.start(); } /** * Returns the CallableClient corresponding to a given private key. * * @param privateKey private key for client * @return entry identified by privateKey * @throws IllegalStateException if privateKey is unknown */ private Entry getEntry( String privateKey ) { Object ent = entryMap_.get( privateKey ); if ( ent instanceof Entry ) { return (Entry) ent; } else { throw new IllegalStateException( "Client is not listening" ); } } } /** * Utility class to aggregate information about a client. */ private static class Entry { final HubConnection connection_; final CallableClient callable_; /** * Constructor. * * @param connection hub connection * @param callable callable client */ Entry( HubConnection connection, CallableClient callable ) { connection_ = connection; callable_ = callable; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/HubActor.java000066400000000000000000000133451356450004300250660ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.util.List; import java.util.Map; import org.astrogrid.samp.client.SampException; /** * Defines the XML-RPC methods which must be implemented by a * Standard Profile hub. * * @author Mark Taylor * @since 15 Jul 2008 */ interface HubActor { /** * Throws an exception if service is not operating. */ void ping() throws SampException; /** * Throws an exception if service is not operating. * * @param privateKey ignored */ void ping( String privateKey ) throws SampException; /** * Registers a new client and returns a map with registration information. * * @param secret registration password * @return {@link org.astrogrid.samp.RegInfo}-like map. */ Map register( String secret ) throws SampException; /** * Unregisters a registered client. * * @param privateKey calling client private key */ void unregister( String privateKey ) throws SampException; /** * Sets the XML-RPC URL to use for callbacks for a callable client. * * @param privateKey calling client private key * @param url XML-RPC endpoint for client API callbacks */ void setXmlrpcCallback( String privateKey, String url ) throws SampException; /** * Declares metadata for the calling client. * * @param privateKey calling client private key * @param meta {@link org.astrogrid.samp.Metadata}-like map */ void declareMetadata( String privateKey, Map meta ) throws SampException; /** * Returns metadata for a given client. * * @param privateKey calling client private key * @param clientId public ID for client whose metadata is required * @return {@link org.astrogrid.samp.Metadata}-like map */ Map getMetadata( String privateKey, String clientId ) throws SampException; /** * Declares subscription information for the calling client. * * @param privateKey calling client private key * @param subs {@link org.astrogrid.samp.Subscriptions}-like map */ void declareSubscriptions( String privateKey, Map subs ) throws SampException; /** * Returns subscriptions for a given client. * * @param privateKey calling client private key * @return {@link org.astrogrid.samp.Subscriptions}-like map */ Map getSubscriptions( String privateKey, String clientId ) throws SampException; /** * Returns a list of the public-ids of all currently registered clients. * * @param privateKey calling client private key * @return list of Strings */ List getRegisteredClients( String privateKey ) throws SampException; /** * Returns a map of the clients subscribed to a given MType. * * @param privateKey calling client private key * @param mtype MType of interest * @return map in which the keys are the public-ids of clients subscribed * to mtype */ Map getSubscribedClients( String privateKey, String mtype ) throws SampException; /** * Sends a message to a given client without wanting a response. * * @param privateKey calling client private key * @param recipientId public-id of client to receive message * @param msg {@link org.astrogrid.samp.Message}-like map */ void notify( String privateKey, String recipientId, Map msg ) throws SampException; /** * Sends a message to all subscribed clients without wanting a response. * * @param privateKey calling client private key * @param msg {@link org.astrogrid.samp.Message}-like map * @return list of public-ids for clients to which the notify will be sent */ List notifyAll( String privateKey, Map msg ) throws SampException; /** * Sends a message to a given client expecting a response. * * @param privateKey calling client private key * @param recipientId public-id of client to receive message * @param msgTag arbitrary string tagging this message for caller's * benefit * @param msg {@link org.astrogrid.samp.Message}-like map * @return message ID */ String call( String privateKey, String recipientId, String msgTag, Map msg ) throws SampException; /** * Sends a message to all subscribed clients expecting responses. * * @param privateKey calling client private key * @param msgTag arbitrary string tagging this message for caller's * benefit * @param msg {@link org.astrogrid.samp.Message}-like map * @return public-id->msg-id map for clients to which an attempt to * send the call will be made */ Map callAll( String privateKey, String msgTag, Map msg ) throws SampException; /** * Sends a message synchronously to a client. * * @param privateKey calling client private key * @param recipientId public-id of client to receive message * @param msg {@link org.astrogrid.samp.Message}-like map * @param timeout timeout in seconds encoded as a SAMP int * @return {@link org.astrogrid.samp.Response}-like map */ Map callAndWait( String privateKey, String recipientId, Map msg, String timeout ) throws SampException; /** * Responds to a previously sent message. * * @param privateKey calling client private key * @param msgId ID associated with earlier send * @param response {@link org.astrogrid.samp.Response}-like map */ void reply( String privateKey, String msgId, Map response ) throws SampException; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/HubMode.java000066400000000000000000000301051356450004300246730ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.awt.AWTException; import java.awt.Image; import java.awt.MenuItem; import java.awt.PopupMenu; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.logging.Logger; import javax.swing.JFrame; import org.astrogrid.samp.Client; import org.astrogrid.samp.gui.GuiHubService; import org.astrogrid.samp.gui.MessageTrackerHubService; import org.astrogrid.samp.gui.SysTray; import org.astrogrid.samp.hub.HubService; import org.astrogrid.samp.hub.BasicHubService; /** * Specifies a particular hub implementation for use with {@link HubRunner}. * * @author Mark Taylor * @since 20 Nov 2008 * @deprecated use {@link org.astrogrid.samp.hub.HubServiceMode} with * {@link org.astrogrid.samp.hub.Hub} instead */ public abstract class HubMode { // This class looks like an enumeration-type class to external users. // It is actually a HubService factory. private final String name_; private final boolean isDaemon_; private static final Logger logger_ = Logger.getLogger( HubMode.class.getName() ); /** Hub mode with no GUI representation of hub operations. */ public static final HubMode NO_GUI; /** Hub mode with a GUI representation of connected clients. */ public static final HubMode CLIENT_GUI; /** Hub mode with a GUI representation of clients and messages. */ public static HubMode MESSAGE_GUI; /** Array of available hub modes. */ private static final HubMode[] KNOWN_MODES = new HubMode[] { NO_GUI = createBasicHubMode( "no-gui" ), CLIENT_GUI = createGuiHubMode( "client-gui" ), MESSAGE_GUI = createMessageTrackerHubMode( "msg-gui" ), }; /** * Constructor. * * @param name mode name * @param isDaemon true if the hub will start only daemon threads */ HubMode( String name, boolean isDaemon ) { name_ = name; isDaemon_ = isDaemon; } /** * Returns a new HubService object. * * @param random random number generator * @param runners 1-element array of HubRunners - this should be * populated with the runner once it has been constructed */ abstract HubService createHubService( Random random, HubRunner[] runners ); /** * Indicates whether the hub service will start only daemon threads. * If it returns true, the caller may need to make sure that the * JVM doesn't stop too early. * * @return true iff no non-daemon threads will be started by the service */ boolean isDaemon() { return isDaemon_; } /** * Returns this mode's name. * * @return mode name */ String getName() { return name_; } public String toString() { return name_; } /** * Returns one of the known modes which has a name as given. * * @param name mode name (case-insensitive) * @return mode with given name, or null if none known */ public static HubMode getModeFromName( String name ) { HubMode[] modes = KNOWN_MODES; for ( int im = 0; im < modes.length; im++ ) { HubMode mode = modes[ im ]; if ( mode.name_.equalsIgnoreCase( name ) ) { return mode; } } return null; } /** * Returns an array of the hub modes which can actually be used. * * @return available mode list */ public static HubMode[] getAvailableModes() { List modeList = new ArrayList(); for ( int i = 0; i < KNOWN_MODES.length; i++ ) { HubMode mode = KNOWN_MODES[ i ]; if ( ! ( mode instanceof BrokenHubMode ) ) { modeList.add( mode ); } } return (HubMode[]) modeList.toArray( new HubMode[ 0 ] ); } /** * Used to perform common configuration of hub display windows * for GUI-type hub modes. * * @param frame hub window * @param runners 1-element array which will contain an associated * hub runner object if one exists */ static void configureHubWindow( JFrame frame, HubRunner[] runners ) { SysTray sysTray = SysTray.getInstance(); if ( sysTray.isSupported() ) { try { configureWindowForSysTray( frame, runners, sysTray ); } catch ( AWTException e ) { logger_.warning( "Failed to install in system tray: " + e ); configureWindowBasic( frame, runners ); } logger_.info( "Hub started in system tray" ); } else { logger_.info( "System tray not supported: displaying hub window" ); configureWindowBasic( frame, runners ); } } /** * Performs common configuration of hub display window without * system tray functionality. * @param frame hub window * @param runners 1-element array which will contain an associated * hub runner object if one exists */ private static void configureWindowBasic( JFrame frame, final HubRunner[] runners ) { frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); frame.addWindowListener( new WindowAdapter() { public void windowClosed( WindowEvent evt ) { HubRunner runner = runners[ 0 ]; if ( runner != null ) { runner.shutdown(); } } } ); frame.setVisible( true ); } /** * Performs common configuration of hub display window with * system tray functionality. * * @param frame hub window * @param runners 1-element array which will contain an associated * hub runner object if one exists * @param sysTray system tray facade object */ private static void configureWindowForSysTray( final JFrame frame, final HubRunner[] runners, final SysTray sysTray ) throws AWTException { /* Prepare the items for display in the tray icon popup menu. */ final MenuItem showItem; final MenuItem hideItem; final MenuItem stopItem; MenuItem[] items = new MenuItem[] { showItem = new MenuItem( "Show Hub Window" ), hideItem = new MenuItem( "Hide Hub Window" ), stopItem = new MenuItem( "Stop Hub" ), }; ActionListener iconListener = new ActionListener() { public void actionPerformed( ActionEvent evt ) { frame.setVisible( true ); showItem.setEnabled( false ); hideItem.setEnabled( true ); } }; /* Construct and install the tray icon. */ Image im = Toolkit.getDefaultToolkit() .createImage( Client.class.getResource( "images/hub.png" ) ); String tooltip = "SAMP Hub"; PopupMenu popup = new PopupMenu(); final Object trayIcon = sysTray.addIcon( im, tooltip, popup, iconListener ); /* Arrange for the menu items to do something appropriate when * triggered. */ ActionListener popListener = new ActionListener() { public void actionPerformed( ActionEvent evt ) { String cmd = evt.getActionCommand(); if ( cmd.equals( showItem.getActionCommand() ) || cmd.equals( hideItem.getActionCommand() ) ) { boolean visible = cmd.equals( showItem.getActionCommand() ); frame.setVisible( visible ); showItem.setEnabled( ! visible ); hideItem.setEnabled( visible ); } else if ( cmd.equals( stopItem.getActionCommand() ) ) { HubRunner runner = runners[ 0 ]; if ( runner != null ) { runner.shutdown(); } try { sysTray.removeIcon( trayIcon ); } catch ( AWTException e ) { logger_.warning( e.toString() ); } frame.dispose(); } } }; for ( int i = 0; i < items.length; i++ ) { items[ i ].addActionListener( popListener ); popup.add( items[ i ] ); } /* Arrange that a manual window close will set the action states * correctly. */ frame.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); frame.addWindowListener( new WindowAdapter() { public void windowClosed( WindowEvent evt ) { showItem.setEnabled( true ); hideItem.setEnabled( false ); } } ); /* Set to initial state. */ popListener.actionPerformed( new ActionEvent( frame, 0, hideItem.getActionCommand() ) ); } /** * Constructs a mode for BasicHubService. * * @return non-gui mode */ private static HubMode createBasicHubMode( String name ) { try { return new HubMode( name, true ) { HubService createHubService( Random random, HubRunner[] runners ) { return new BasicHubService( random ); } }; } catch ( Throwable e ) { return new BrokenHubMode( name, e ); } } /** * Constructs a mode for GuiHubService. * * @return mode without message tracking */ private static HubMode createGuiHubMode( String name ) { try { GuiHubService.class.getName(); return new HubMode( name, false ) { HubService createHubService( Random random, final HubRunner[] runners ) { return new GuiHubService( random ) { public void start() { super.start(); configureHubWindow( createHubWindow(), runners ); } }; } }; } catch ( Throwable e ) { return new BrokenHubMode( name, e ); } } /** * Constructs a mode for MessageTrackerHubService. * * @return mode with message tracking */ private static HubMode createMessageTrackerHubMode( String name ) { try { MessageTrackerHubService.class.getName(); return new HubMode( name, false ) { HubService createHubService( Random random, final HubRunner[] runners ) { return new MessageTrackerHubService( random ) { public void start() { super.start(); configureHubWindow( createHubWindow(), runners ); } }; } }; } catch ( Throwable e ) { return new BrokenHubMode( name, e ); } } /** * HubMode implemenetation for modes which cannot be used because they * rely on classes unavailable at runtime. */ private static class BrokenHubMode extends HubMode { private final Throwable error_; /** * Constructor. * * @param name mode name * @param error error explaining why mode is unavailable for use */ BrokenHubMode( String name, Throwable error ) { super( name, false ); error_ = error; } HubService createHubService( Random random, HubRunner[] runners ) { throw new RuntimeException( "Hub mode " + getName() + " unavailable", error_ ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/HubRunner.java000066400000000000000000000643211356450004300252670ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; 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.OutputStream; import java.net.InetAddress; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.hub.Hub; import org.astrogrid.samp.hub.HubService; import org.astrogrid.samp.hub.KeyGenerator; import org.astrogrid.samp.hub.LockWriter; import org.astrogrid.samp.hub.MessageRestriction; import org.astrogrid.samp.hub.ProfileToken; import org.astrogrid.samp.httpd.ServerResource; import org.astrogrid.samp.httpd.UtilServer; /** * Runs a SAMP hub using the SAMP Standard Profile. * The {@link #start} method must be called to start it up. * *

The {@link #main} method can be used to launch a hub from * the command line. Use the -help flag for more information. * * @author Mark Taylor * @since 15 Jul 2008 * @deprecated use {@link org.astrogrid.samp.hub.Hub} instead */ public class HubRunner { private final SampXmlRpcClientFactory xClientFactory_; private final SampXmlRpcServerFactory xServerFactory_; private final HubService hub_; private final File lockfile_; private URL lockUrl_; private LockInfo lockInfo_; private SampXmlRpcServer server_; private HubXmlRpcHandler hubHandler_; private boolean shutdown_; private final static ProfileToken STANDARD_PROFILE = new ProfileToken() { public String getProfileName() { return "Standard"; } public MessageRestriction getMessageRestriction() { return null; } }; private final static Logger logger_ = Logger.getLogger( HubRunner.class.getName() ); private final static Random random_ = KeyGenerator.createRandom(); /** * Constructor. * If the supplied lockfile is null, no lockfile will * be written at hub startup. * * @param xClientFactory XML-RPC client factory implementation * @param xServerFactory XML-RPC server implementation * @param hub object providing hub services * @param lockfile location to use for hub lockfile, or null */ public HubRunner( SampXmlRpcClientFactory xClientFactory, SampXmlRpcServerFactory xServerFactory, HubService hub, File lockfile ) { xClientFactory_ = xClientFactory; xServerFactory_ = xServerFactory; hub_ = hub; lockfile_ = lockfile; logger_.warning( "Class " + HubRunner.class.getName() + " is deprecated; use " + Hub.class.getName() + " instead." ); } /** * Starts the hub and writes the lockfile. * * @throws IOException if a hub is already running or an error occurs */ public void start() throws IOException { // Check for running or moribund hub. if ( lockfile_ != null && lockfile_.exists() ) { if ( isHubAlive( xClientFactory_, lockfile_ ) ) { throw new IOException( "A hub is already running" ); } else { logger_.warning( "Overwriting " + lockfile_ + " lockfile " + "for apparently dead hub" ); lockfile_.delete(); } } // Start up server. try { server_ = xServerFactory_.getServer(); } catch ( IOException e ) { throw e; } catch ( Exception e ) { throw (IOException) new IOException( "Can't start XML-RPC server" ) .initCause( e ); } // Start the hub service. hub_.start(); String secret = createSecret(); ClientProfile connectionFactory = new ClientProfile() { public HubConnection register() throws SampException { return hub_.register( STANDARD_PROFILE ); } public boolean isHubRunning() { return hub_.isHubRunning(); } }; hubHandler_ = new HubXmlRpcHandler( xClientFactory_, connectionFactory, secret, new KeyGenerator( "k:", 16, random_ ) ); server_.addHandler( hubHandler_ ); // Ensure tidy up in case of JVM shutdown. Runtime.getRuntime().addShutdownHook( new Thread( "HubRunner shutdown" ) { public void run() { shutdown(); } } ); // Prepare lockfile information. lockInfo_ = new LockInfo( secret, server_.getEndpoint().toString() ); lockInfo_.put( "hub.impl", hub_.getClass().getName() ); lockInfo_.put( "hub.start.date", new Date().toString() ); // Write lockfile information to file if required. if ( lockfile_ != null ) { logger_.info( "Writing new lockfile " + lockfile_ ); FileOutputStream out = new FileOutputStream( lockfile_ ); try { writeLockInfo( lockInfo_, out ); try { LockWriter.setLockPermissions( lockfile_ ); logger_.info( "Lockfile permissions set to " + "user access only" ); } catch ( IOException e ) { logger_.log( Level.WARNING, "Failed attempt to change " + lockfile_ + " permissions to user access only" + " - possible security implications", e ); } } finally { try { out.close(); } catch ( IOException e ) { logger_.log( Level.WARNING, "Error closing lockfile?", e ); } } } } /** * Shuts down the hub and tidies up. * May harmlessly be called multiple times. */ public synchronized void shutdown() { // Return if we have already done this. if ( shutdown_ ) { return; } shutdown_ = true; // Delete the lockfile if it exists and if it is the one originally // written by this runner. if ( lockfile_ != null ) { if ( lockfile_.exists() ) { try { LockInfo lockInfo = readLockFile( lockfile_ ); if ( lockInfo.getSecret() .equals( lockInfo_.getSecret() ) ) { assert lockInfo.equals( lockInfo_ ); boolean deleted = lockfile_.delete(); logger_.info( "Lockfile " + lockfile_ + " " + ( deleted ? "deleted" : "deletion attempt failed" ) ); } else { logger_.warning( "Lockfile " + lockfile_ + " has been " + " overwritten - not deleting" ); } } catch ( Throwable e ) { logger_.log( Level.WARNING, "Failed to delete lockfile " + lockfile_, e ); } } else { logger_.warning( "Lockfile " + lockfile_ + " has disappeared" ); } } // Withdraw service of the lockfile, if one has been published. if ( lockUrl_ != null ) { try { UtilServer.getInstance().getResourceHandler() .removeResource( lockUrl_ ); } catch ( IOException e ) { logger_.warning( "Failed to withdraw lockfile URL" ); } lockUrl_ = null; } // Shut down the hub service if exists. This sends out shutdown // messages to registered clients. if ( hub_ != null ) { try { hub_.shutdown(); } catch ( Throwable e ) { logger_.log( Level.WARNING, "Hub service shutdown failed", e ); } } // Remove the hub XML-RPC handler from the server. if ( hubHandler_ != null && server_ != null ) { server_.removeHandler( hubHandler_ ); server_ = null; } lockInfo_ = null; } /** * Returns the HubService object used by this runner. * * @return hub service */ public HubService getHub() { return hub_; } /** * Returns the lockfile information associated with this object. * Only present after {@link #start} has been called. * * @return lock info */ public LockInfo getLockInfo() { return lockInfo_; } /** * Returns an HTTP URL at which the lockfile for this hub can be found. * The first call to this method causes the lockfile to be published * in this way; subsequent calls return the same value. * *

Use this with care; publishing your lockfile means that other people * can connect to your hub and potentially do disruptive things. * * @return lockfile information URL */ public URL publishLockfile() throws IOException { if ( lockUrl_ == null ) { ByteArrayOutputStream infoStrm = new ByteArrayOutputStream(); writeLockInfo( lockInfo_, infoStrm ); infoStrm.close(); final byte[] infoBuf = infoStrm.toByteArray(); URL url = UtilServer.getInstance().getResourceHandler() .addResource( "samplock", new ServerResource() { public long getContentLength() { return infoBuf.length; } public String getContentType() { return "text/plain"; } public void writeBody( OutputStream out ) throws IOException { out.write( infoBuf ); } } ); // Attempt to replace whatever host name is used by the FQDN, // for maximal usefulness to off-host clients. try { url = new URL( url.getProtocol(), InetAddress.getLocalHost() .getCanonicalHostName(), url.getPort(), url.getFile() ); } catch ( IOException e ) { } lockUrl_ = url; } return lockUrl_; } /** * Used to generate the registration password. May be overridden. * * @return pasword */ public String createSecret() { return Long.toHexString( random_.nextLong() ); } /** * Attempts to determine whether a given lockfile corresponds to a hub * which is still alive. * * @param xClientFactory XML-RPC client factory implementation * @param lockfile lockfile location * @return true if the hub described at lockfile appears * to be alive and well */ private static boolean isHubAlive( SampXmlRpcClientFactory xClientFactory, File lockfile ) { LockInfo info; try { info = readLockFile( lockfile ); } catch ( Exception e ) { logger_.log( Level.WARNING, "Failed to read lockfile", e ); return false; } if ( info == null ) { return false; } URL xurl = info.getXmlrpcUrl(); if ( xurl != null ) { try { xClientFactory.createClient( xurl ) .callAndWait( "samp.hub.ping", new ArrayList() ); return true; } catch ( Exception e ) { logger_.log( Level.WARNING, "Hub ping method failed", e ); return false; } } else { logger_.warning( "No XMLRPC URL in lockfile" ); return false; } } /** * Reads lockinfo from a file. * * @param lockFile file * @return info from file */ private static LockInfo readLockFile( File lockFile ) throws IOException { return LockInfo.readLockFile( new FileInputStream( lockFile ) ); } /** * Writes lockfile information to a given output stream. * The stream is not closed. * * @param info lock info to write * @param out destination stream */ private static void writeLockInfo( LockInfo info, OutputStream out ) throws IOException { LockWriter writer = new LockWriter( out ); writer.writeComment( "SAMP Standard Profile lockfile written " + new Date() ); writer.writeComment( "Note contact URL hostname may be " + "configured using " + SampUtils.LOCALHOST_PROP + " property" ); writer.writeAssignments( info ); out.flush(); } /** * Main method. Starts a hub. * Use "-help" flag for more information. * * @param args command-line arguments */ public static void main( String[] args ) throws IOException { int status = runMain( args ); if ( status != 0 ) { System.exit( status ); } } /** * Does the work for running the {@link #main} method. * System.exit() is not called from this method. * Use "-help" flag for more information. * * @param args command-line arguments * @return 0 means success, non-zero means error status */ public static int runMain( String[] args ) throws IOException { StringBuffer ubuf = new StringBuffer(); ubuf.append( "\n Usage:" ) .append( "\n " ) .append( HubRunner.class.getName() ) .append( "\n " ) .append( " [-help]" ) .append( " [-/+verbose]" ) .append( "\n " ) .append( " [-mode " ); HubMode[] modes = HubMode.getAvailableModes(); for ( int im = 0; im < modes.length; im++ ) { if ( im > 0 ) { ubuf.append( '|' ); } ubuf.append( modes[ im ].getName() ); } ubuf.append( ']' ) .append( " [-secret ]" ) .append( " [-httplock]" ) .append( "\n" ); String usage = ubuf.toString(); List argList = new ArrayList( Arrays.asList( args ) ); HubMode hubMode = HubMode.MESSAGE_GUI; if ( ! Arrays.asList( HubMode.getAvailableModes() ) .contains( hubMode ) ) { hubMode = HubMode.NO_GUI; } int verbAdjust = 0; XmlRpcKit xmlrpc = null; String secret = null; boolean httplock = false; for ( Iterator it = argList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.equals( "-mode" ) && it.hasNext() ) { it.remove(); String mode = (String) it.next(); it.remove(); hubMode = HubMode.getModeFromName( mode ); if ( hubMode == null ) { System.err.println( "Unknown mode " + mode ); System.err.println( usage ); return 1; } } else if ( arg.equals( "-secret" ) && it.hasNext() ) { it.remove(); secret = (String) it.next(); it.remove(); } else if ( arg.equals( "-httplock" ) ) { it.remove(); httplock = true; } else if ( arg.startsWith( "-v" ) ) { it.remove(); verbAdjust--; } else if ( arg.startsWith( "+v" ) ) { it.remove(); verbAdjust++; } else if ( arg.startsWith( "-h" ) ) { it.remove(); System.out.println( usage ); return 0; } else { System.err.println( usage ); return 1; } } assert argList.isEmpty(); // Adjust logging in accordance with verboseness flags. int logLevel = Level.WARNING.intValue() + 100 * verbAdjust; Logger.getLogger( "org.astrogrid.samp" ) .setLevel( Level.parse( Integer.toString( logLevel ) ) ); // Get the location of the lockfile to write, if any. final File lockfile; if ( httplock ) { lockfile = null; } else { URL lockUrl = StandardClientProfile.getLockUrl(); File f = SampUtils.urlToFile( lockUrl ); if ( f == null ) { System.err.println( "Can't write lockfile to " + lockUrl ); System.err.println( "Try resetting " + DefaultClientProfile.HUBLOC_ENV + " environment variable." ); return 1; } else { lockfile = f; } } // Start the hub. HubRunner runner = runHub( hubMode, xmlrpc, secret, lockfile ); // If the lockfile is not the default one, write a message through // the logging system. URL lockfileUrl = httplock ? runner.publishLockfile() : SampUtils.fileToUrl( lockfile ); boolean isDflt = StandardClientProfile.getDefaultLockUrl().toString() .equals( lockfileUrl.toString() ); String hubassign = DefaultClientProfile.HUBLOC_ENV + "=" + StandardClientProfile.STDPROFILE_HUB_PREFIX + lockfileUrl; logger_.log( isDflt ? Level.INFO : Level.WARNING, hubassign ); // For non-GUI case block indefinitely otherwise the hub (which uses // a daemon thread) will not just exit immediately. if ( hubMode.isDaemon() ) { Object lock = new String( "Indefinite" ); synchronized ( lock ) { try { lock.wait(); } catch ( InterruptedException e ) { } } } // Success return. return 0; } /** * Static method which may be used to start a SAMP hub programmatically. * The returned hub is running (start has been called). * *

If the hub mode corresponds to one of the GUI options, * one of two things will happen. An attempt will be made to install * an icon in the "system tray"; if this is successful, the attached * popup menu will provide options for displaying the hub window and * for shutting it down. If no system tray is available, the hub window * will be posted directly, and the hub will shut down when this window * is closed. System tray functionality is only available when running * under Java 1.6 or later, and when using a suitable display manager. * * @param hubMode hub mode * @param xmlrpc XML-RPC implementation; * automatically determined if null * @return running hub */ public static HubRunner runHub( HubMode hubMode, XmlRpcKit xmlrpc ) throws IOException { return runHub( hubMode, xmlrpc, null, SampUtils .urlToFile( StandardClientProfile.getLockUrl() ) ); } /** * Static method which may be used to start a SAMP hub programmatically, * with a supplied samp.secret string. * The returned hub is running (start has been called). * *

If the hub mode corresponds to one of the GUI options, * one of two things will happen. An attempt will be made to install * an icon in the "system tray"; if this is successful, the attached * popup menu will provide options for displaying the hub window and * for shutting it down. If no system tray is available, the hub window * will be posted directly, and the hub will shut down when this window * is closed. System tray functionality is only available when running * under Java 1.6 or later, and when using a suitable display manager. * * @param hubMode hub mode * @param xmlrpc XML-RPC implementation; * automatically determined if null * @param secret samp.secret string to be used for hub connection; * chosen at random if null * @param lockfile location of lockfile to write, * or null for lock to be provided by HTTP * @return running hub */ public static HubRunner runHub( HubMode hubMode, XmlRpcKit xmlrpc, final String secret, File lockfile ) throws IOException { if ( xmlrpc == null ) { xmlrpc = XmlRpcKit.getInstance(); } HubRunner[] hubRunners = new HubRunner[ 1 ]; HubRunner runner = new HubRunner( xmlrpc.getClientFactory(), xmlrpc.getServerFactory(), hubMode.createHubService( random_, hubRunners ), lockfile ) { public String createSecret() { return secret == null ? super.createSecret() : secret; } }; hubRunners[ 0 ] = runner; runner.start(); return runner; } /** * Static method which will attempt to start a hub running in * an external JVM. The resulting hub can therefore outlast the * lifetime of the current application. * Because of the OS interaction required, it's hard to make this * bulletproof, and it may fail without an exception, but we do our best. * * @param hubMode hub mode * @see #checkExternalHubAvailability */ public static void runExternalHub( HubMode hubMode ) throws IOException { String classpath = System.getProperty( "java.class.path" ); if ( classpath == null || classpath.trim().length() == 0 ) { throw new IOException( "No classpath available - JNLP context?" ); } File javaHome = new File( System.getProperty( "java.home" ) ); File javaExec = new File( new File( javaHome, "bin" ), "java" ); String javacmd = ( javaExec.exists() && ! javaExec.isDirectory() ) ? javaExec.toString() : "java"; String[] propagateProps = new String[] { XmlRpcKit.IMPL_PROP, UtilServer.PORT_PROP, SampUtils.LOCALHOST_PROP, "java.awt.Window.locationByPlatform", }; List argList = new ArrayList(); argList.add( javacmd ); for ( int ip = 0; ip < propagateProps.length; ip++ ) { String propName = propagateProps[ ip ]; String propVal = System.getProperty( propName ); if ( propVal != null ) { argList.add( "-D" + propName + "=" + propVal ); } } argList.add( "-classpath" ); argList.add( classpath ); argList.add( HubRunner.class.getName() ); argList.add( "-mode" ); argList.add( hubMode.toString() ); String[] args = (String[]) argList.toArray( new String[ 0 ] ); StringBuffer cmdbuf = new StringBuffer(); for ( int iarg = 0; iarg < args.length; iarg++ ) { if ( iarg > 0 ) { cmdbuf.append( ' ' ); } cmdbuf.append( args[ iarg ] ); } logger_.info( "Starting external hub" ); logger_.info( cmdbuf.toString() ); execBackground( args ); } /** * Attempts to determine whether an external hub can be started using * {@link #runExternalHub}. If it can be determined that such an * attempt would fail, this method will throw an exception with * an informative message. This method succeeding is not a guarantee * that an external hub can be started successfullly. * The behaviour of this method is not expected to change over the * lifetime of a given JVM. */ public static void checkExternalHubAvailability() throws IOException { String classpath = System.getProperty( "java.class.path" ); if ( classpath == null || classpath.trim().length() == 0 ) { throw new IOException( "No classpath available - JNLP context?" ); } if ( System.getProperty( "jnlpx.jvm" ) != null ) { throw new IOException( "Running under WebStart" + " - external hub not likely to work" ); } } /** * Executes a command in a separate process, and discards any stdout * or stderr output generated by it. * Simply calling Runtime.exec can block the process * until its output is consumed. * * @param cmdarray array containing the command to call and its args */ private static void execBackground( String[] cmdarray ) throws IOException { Process process = Runtime.getRuntime().exec( cmdarray ); discardBytes( process.getInputStream() ); discardBytes( process.getErrorStream() ); } /** * Ensures that any bytes from a given input stream are discarded. * * @param in input stream */ private static void discardBytes( final InputStream in ) { Thread eater = new Thread( "StreamEater" ) { public void run() { try { while ( in.read() >= 0 ) {} in.close(); } catch ( IOException e ) { } } }; eater.setDaemon( true ); eater.start(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/HubXmlRpcHandler.java000066400000000000000000000205201356450004300265120ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.hub.KeyGenerator; /** * SampXmlRpcHandler implementation which passes Standard Profile-type XML-RPC * calls to a hub connection factory to provide a Standard Profile hub server. * * @author Mark Taylor * @since 15 Jul 2008 */ class HubXmlRpcHandler extends ActorHandler { /** * Constructor. * * @param xClientFactory XML-RPC client factory implementation * @param profile hub connection factory * @param secret password required for client registration */ public HubXmlRpcHandler( SampXmlRpcClientFactory xClientFactory, ClientProfile profile, String secret, KeyGenerator keyGen ) { super( "samp.hub.", HubActor.class, new HubActorImpl( xClientFactory, profile, secret, keyGen ) ); } protected Object invokeMethod( Method method, Object obj, Object[] args ) throws IllegalAccessException, InvocationTargetException { return method.invoke( obj, args ); } /** * Implementation of the {@link HubActor} interface which does * the work for this class. * Apart from a few methods which have Standard-Profile-specific * aspects, the work is simply delegated to the hub connection factory. */ private static class HubActorImpl implements HubActor { private final SampXmlRpcClientFactory xClientFactory_; private final ClientProfile profile_; private final String secret_; private final KeyGenerator keyGen_; private final Map clientMap_; /** * Constructor. * * @param xClientFactory XML-RPC client factory implementation * @param profile hub connection factory * @param secret password required for client registration * @param keyGen generator for private keys */ HubActorImpl( SampXmlRpcClientFactory xClientFactory, ClientProfile profile, String secret, KeyGenerator keyGen ) { xClientFactory_ = xClientFactory; profile_ = profile; secret_ = secret; keyGen_ = keyGen; clientMap_ = Collections.synchronizedMap( new HashMap() ); } public Map register( String secret ) throws SampException { if ( ! profile_.isHubRunning() ) { throw new SampException( "Hub not running" ); } else if ( secret_.equals( secret ) ) { HubConnection connection = profile_.register(); if ( connection == null ) { throw new SampException( "Hub is not running" ); } String privateKey = keyGen_.next(); RegInfo regInfo = connection.getRegInfo(); regInfo.put( RegInfo.PRIVATEKEY_KEY, privateKey ); clientMap_.put( privateKey, connection ); return regInfo; } else { throw new SampException( "Bad password" ); } } public void unregister( String privateKey ) throws SampException { HubConnection connection = (HubConnection) clientMap_.remove( privateKey ); if ( connection == null ) { throw new SampException( "Unknown private key" ); } else { connection.unregister(); } } public void ping( String privateKey ) throws SampException { getConnection( privateKey ).ping(); } public void setXmlrpcCallback( String privateKey, String surl ) throws SampException { SampXmlRpcClient xClient; try { xClient = xClientFactory_.createClient( new URL( surl ) ); } catch ( MalformedURLException e ) { throw new SampException( "Bad URL: " + surl, e ); } catch ( IOException e ) { throw new SampException( "No connection: " + e.getMessage(), e ); } getConnection( privateKey ) .setCallable( new XmlRpcCallableClient( xClient, privateKey ) ); } public void declareMetadata( String privateKey, Map metadata ) throws SampException { getConnection( privateKey ).declareMetadata( metadata ); } public Map getMetadata( String privateKey, String clientId ) throws SampException { return getConnection( privateKey ).getMetadata( clientId ); } public void declareSubscriptions( String privateKey, Map subs ) throws SampException { getConnection( privateKey ).declareSubscriptions( subs ); } public Map getSubscriptions( String privateKey, String clientId ) throws SampException { return getConnection( privateKey ).getSubscriptions( clientId ); } public List getRegisteredClients( String privateKey ) throws SampException { return Arrays.asList( getConnection( privateKey ) .getRegisteredClients() ); } public Map getSubscribedClients( String privateKey, String mtype ) throws SampException { return getConnection( privateKey ).getSubscribedClients( mtype ); } public void notify( String privateKey, String recipientId, Map msg ) throws SampException { getConnection( privateKey ).notify( recipientId, msg ); } public List notifyAll( String privateKey, Map msg ) throws SampException { return getConnection( privateKey ).notifyAll( msg ); } public String call( String privateKey, String recipientId, String msgTag, Map msg ) throws SampException { return getConnection( privateKey ).call( recipientId, msgTag, msg ); } public Map callAll( String privateKey, String msgTag, Map msg ) throws SampException { return getConnection( privateKey ).callAll( msgTag, msg ); } public Map callAndWait( String privateKey, String recipientId, Map msg, String timeoutStr ) throws SampException { int timeout; try { timeout = SampUtils.decodeInt( timeoutStr ); } catch ( Exception e ) { throw new SampException( "Bad timeout format" + " (should be SAMP int)", e ); } return getConnection( privateKey ) .callAndWait( recipientId, msg, timeout ); } public void reply( String privateKey, String msgId, Map response ) throws SampException { getConnection( privateKey ).reply( msgId, response ); } public void ping() throws SampException { if ( ! profile_.isHubRunning() ) { throw new SampException( "Hub is stopped" ); } } /** * Returns the HubConnection associated with a private key used * by this hub actor. * * @param privateKey private key * @return connection for privateKey */ private HubConnection getConnection( String privateKey ) throws SampException { HubConnection connection = (HubConnection) clientMap_.get( privateKey ); if ( connection == null ) { throw new SampException( "Unknown private key" ); } else { return connection; } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/LockInfo.java000066400000000000000000000170351356450004300250630ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Iterator; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.logging.Logger; import org.astrogrid.samp.DataException; import org.astrogrid.samp.SampMap; import org.astrogrid.samp.SampUtils; /** * Represents the information read from a SAMP Standard Profile Lockfile. * This contains a key-value entry for each assignment read from the file. * Any non-assignment lines are not represented by this object. * * @author Mark Taylor * @since 14 Jul 2008 */ public class LockInfo extends SampMap { private static final Logger logger_ = Logger.getLogger( LockInfo.class.getName() ); /** Key for opaque text string required by the hub for registration. */ public static final String SECRET_KEY = "samp.secret"; /** Key for XML-RPC endpoint for communication with the hub. */ public static final String XMLRPCURL_KEY = "samp.hub.xmlrpc.url"; /** Key for the SAMP Standard Profile version implemented by the hub. */ public static final String VERSION_KEY = "samp.profile.version"; private static final String[] KNOWN_KEYS = new String[] { SECRET_KEY, XMLRPCURL_KEY, VERSION_KEY, }; /** SAMP Standard Profile version for this toolkit implementation. */ public static final String DEFAULT_VERSION_VALUE = "1.3"; private static final Pattern TOKEN_REGEX = Pattern.compile( "[a-zA-Z0-9\\-_\\.]+" ); private static final Pattern ASSIGNMENT_REGEX = Pattern.compile( "(" + TOKEN_REGEX.pattern() + ")=(.*)" ); private static final Pattern COMMENT_REGEX = Pattern.compile( "#[\u0020-\u007f]*" ); /** * Constructs an empty LockInfo. */ public LockInfo() { super( KNOWN_KEYS ); } /** * Constructs a LockInfo based on an existing map. * * @param map map containing initial data for this object */ public LockInfo( Map map ) { this(); putAll( map ); } /** * Constructs a LockInfo from a given SAMP secret and XML-RPC URL. * The version string is set to the default for this toolkit. * * @param secret value for {@link #SECRET_KEY} key * @param xmlrpcurl value for {@link #XMLRPCURL_KEY} key */ public LockInfo( String secret, String xmlrpcurl ) { this(); put( SECRET_KEY, secret ); put( XMLRPCURL_KEY, xmlrpcurl ); put( VERSION_KEY, DEFAULT_VERSION_VALUE ); } /** * Returns the value of the {@link #XMLRPCURL_KEY} key. * * @return hub XML-RPC connection URL */ public URL getXmlrpcUrl() { return getUrl( XMLRPCURL_KEY ); } /** * Returns the value of the {@link #VERSION_KEY} key. * * @return version of the SAMP standard profile implemented */ public String getVersion() { return getString( VERSION_KEY ); } /** * Returns the value of the {@link #SECRET_KEY} key. * * @return password for hub connection */ public String getSecret() { return getString( SECRET_KEY ); } public void check() { super.check(); checkHasKeys( new String[] { SECRET_KEY, XMLRPCURL_KEY, } ); for ( Iterator it = entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); Object key = entry.getKey(); if ( key instanceof String ) { if ( ! TOKEN_REGEX.matcher( key.toString() ).matches() ) { throw new DataException( "Bad key syntax: " + key + " does not match " + TOKEN_REGEX.pattern() ); } } else { throw new DataException( "Map key " + entry.getKey() + " is not a string" ); } Object value = entry.getValue(); if ( value instanceof String ) { String sval = (String) value; for ( int i = 0; i < sval.length(); i++ ) { int c = sval.charAt( i ); if ( c < 0x20 || c > 0x7f ) { throw new DataException( "Value contains illegal " + "character 0x" + Integer.toHexString( c ) ); } } } else { throw new DataException( "Map value " + value + " is not a string" ); } } } /** * Returns a LockInfo as read from a lockfile at a given location. * If the lockfile does not exist, null is returned. * An exception may be thrown if it exists but is cannot be read. * * @param url lockfile location * @return lockfile contents, or null if it is absent */ public static LockInfo readLockFile( URL url ) throws IOException { final InputStream in; File file = SampUtils.urlToFile( url ); if ( file != null ) { if ( file.exists() ) { in = new FileInputStream( file ); } else { return null; } } else { try { in = url.openStream(); } catch ( IOException e ) { return null; } } return readLockFile( in ); } /** * Returns the LockInfo read from a given stream. * The stream is closed if the read is successful. * * @param in input stream to read * @return lockfile information */ public static LockInfo readLockFile( InputStream in ) throws IOException { LockInfo info = new LockInfo(); in = new BufferedInputStream( in ); for ( String line; ( line = readLine( in ) ) != null; ) { Matcher assigner = ASSIGNMENT_REGEX.matcher( line ); if ( assigner.matches() ) { info.put( assigner.group( 1 ), assigner.group( 2 ) ); } else if ( COMMENT_REGEX.matcher( line ).matches() ) { } else if ( line.length() == 0 ) { } else { logger_.warning( "Ignoring lockfile line with bad syntax" ); logger_.info( "Bad line: " + line ); } } in.close(); return info; } /** * Returns a given map as a LockInfo object. * * @param map map * @return lock info */ public static LockInfo asLockInfo( Map map ) { return map instanceof LockInfo ? (LockInfo) map : new LockInfo( map ); } /** * Returns a line from a lockfile-type input stream. * * @param in input stream * @return next line */ private static String readLine( InputStream in ) throws IOException { StringBuffer sbuf = new StringBuffer(); while ( true ) { int c = in.read(); switch ( c ) { case '\r': case '\n': return sbuf.toString(); case -1: return sbuf.length() > 0 ? sbuf.toString() : null; default: sbuf.append( (char) c ); } } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcClient.java000066400000000000000000000026061356450004300265420ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.IOException; import java.net.URL; import java.util.List; /** * Interface for a client which can make XML-RPC calls for SAMP. * The method parameters and return values must be of SAMP-compatible types, * that is only Strings, Lists, and String-keyed Maps are allowed in * the data structures. * * @author Mark Taylor * @since 22 Aug 2008 */ public interface SampXmlRpcClient { /** * Makes a synchronous call, waiting for the response and returning * the result. * * @param method XML-RPC method name * @param params parameters for XML-RPC call (SAMP-compatible) * @return XML-RPC call return value (SAMP-compatible) */ Object callAndWait( String method, List params ) throws IOException; /** * Sends a call, but does not wait around for the response. * If possible, this method should complete quickly. * *

NOTE: it seems to be difficult to implement this method in a * way which is faster than {@link #callAndWait} but does not cause * problems elsewhere (incomplete HTTP responses). It is probably * a good idea to avoid using it if possible. * * @param method XML-RPC method name * @param params parameters for XML-RPC call (SAMP-compatible) */ void callAndForget( String method, List params ) throws IOException; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcClientFactory.java000066400000000000000000000010411356450004300300620ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.IOException; import java.net.URL; /** * Defines a factory which can create clients for communication with * XML-RPC servers. * * @author Mark Taylor * @since 16 Sep 2008 */ public interface SampXmlRpcClientFactory { /** * Returns an XML-RPC client implementation. * * @param endpoint XML-RPC server endpoint * @return client which can communicate with the given endpoint */ SampXmlRpcClient createClient( URL endpoint ) throws IOException; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcHandler.java000066400000000000000000000025211356450004300266750ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.util.List; /** * Interface for an object which can process certain XML-RPC requests. * Used by {@link SampXmlRpcServer}. * * @author Mark Taylor * @since 22 Aug 2008 */ public interface SampXmlRpcHandler { /** * Returns true if this handler should be able to process * given XML-RPC method. * * @param method method name */ boolean canHandleCall( String method ); /** * Processes an XML-RPC call. This method should only be called if * {@link #canHandleCall canHandleCall(method)} returns true. * The params list and the return value must be * SAMP-compatible, that is only Strings, Lists and String-keyed Maps * are allowed in the data structures. * The reqInfo parameter may be used to provide additional * information about the XML-RPC request, for instance the originating * host; this is implementation specific, and may be null. * * @param method XML-RPC method name * @param params XML-RPC parameter list (SAMP-compatible) * @param reqInfo optional additional request information; may be null * @return return value (SAMP-compatible) */ Object handleCall( String method, List params, Object reqInfo ) throws Exception; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcServer.java000066400000000000000000000015601356450004300265700ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.net.URL; /** * Interface for a server which can respond to XML-RPC calls for SAMP. * The method parameters and return values must be of SAMP-compatible types, * that is only Strings, Lists, and String-keyed Maps are allowed in * the data structures. * * @author Mark Taylor * @since 22 Aug 2008 */ public interface SampXmlRpcServer { /** * Returns the server's endpoint. * * @return URL to which XML-RPC requests are POSTed */ URL getEndpoint(); /** * Adds a handler which can service certain XML-RPC methods. * * @param handler handler to add */ void addHandler( SampXmlRpcHandler handler ); /** * Removes a previously-added handler. * * @param handler handler to remove */ void removeHandler( SampXmlRpcHandler handler ); } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/SampXmlRpcServerFactory.java000066400000000000000000000021011356450004300301100ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.IOException; /** * Defines a factory for SampXmlRpcServer instances. * In most cases it will make sense to implement this class so that * a single server instance is constructed lazily, and the same instance * is always returned from the {@link #getServer} method. * This means that the same server can be used for everything that requires * an XML-RPC server, thus keeping resource usage down. * Users of this class must keep this implementation model in mind, * so must not assume that a new instance is returned each time. * But if an implementation wants to return a new instance each time for * some reason, that is permissible. * * @author Mark Taylor * @since 22 Aug 2008 */ public interface SampXmlRpcServerFactory { /** * Returns an XML-RPC server implementation. * Implementations are permitted, but not required, to return the same * object from different calls of this method. * * @return new or re-used server */ SampXmlRpcServer getServer() throws IOException; } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/StandardClientProfile.java000066400000000000000000000166621356450004300276040ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.logging.Logger; import org.astrogrid.samp.DataException; import org.astrogrid.samp.Platform; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * Standard Profile implementation of ClientProfile. * It is normally appropriate to use one of the static methods * to obtain an instance based on a particular XML-RPC implementation. * * @author Mark Taylor * @since 15 Jul 2008 */ public class StandardClientProfile implements ClientProfile { private final SampXmlRpcClientFactory xClientFactory_; private final SampXmlRpcServerFactory xServerFactory_; private static StandardClientProfile defaultInstance_; private static URL dfltLockUrl_; private static URL lockUrl_; private static final Logger logger_ = Logger.getLogger( StandardClientProfile.class.getName() ); /** Filename used for lockfile in home directory by default ({@value}). */ public static final String LOCKFILE_NAME = ".samp"; /** Prefix in SAMP_HUB value indicating lockfile URL ({@value}). */ public static final String STDPROFILE_HUB_PREFIX = "std-lockurl:"; /** * Constructs a profile given client and server factory implementations. * * @param xClientFactory XML-RPC client factory implementation * @param xServerFactory XML-RPC server factory implementation */ public StandardClientProfile( SampXmlRpcClientFactory xClientFactory, SampXmlRpcServerFactory xServerFactory ) { xClientFactory_ = xClientFactory; xServerFactory_ = xServerFactory; } /** * Constructs a profile given an XmlRpcKit object. * * @param xmlrpc XML-RPC implementation */ public StandardClientProfile( XmlRpcKit xmlrpc ) { this( xmlrpc.getClientFactory(), xmlrpc.getServerFactory() ); } public boolean isHubRunning() { try { LockInfo lockInfo = getLockInfo(); if ( lockInfo == null ) { return false; } URL xurl = lockInfo.getXmlrpcUrl(); if ( xurl == null ) { return false; } SampXmlRpcClient xClient = xClientFactory_.createClient( xurl ); xClient.callAndWait( "samp.hub.ping", new ArrayList() ); return true; } catch ( IOException e ) { return false; } } public HubConnection register() throws SampException { LockInfo lockInfo; try { lockInfo = getLockInfo(); } catch ( SampException e ) { throw (SampException) e; } catch ( IOException e ) { throw new SampException( "Error reading lockfile", e ); } if ( lockInfo == null ) { return null; } else { try { lockInfo.check(); } catch ( DataException e ) { String msg = "Incomplete/broken lock file"; try { File lockFile = SampUtils.urlToFile( getLockUrl() ); if ( lockFile != null ) { msg += " - try deleting " + lockFile; } } catch ( IOException e2 ) { // never mind } throw new SampException( msg, e ); } SampXmlRpcClient xClient; URL xurl = lockInfo.getXmlrpcUrl(); try { xClient = xClientFactory_.createClient( xurl ); } catch ( IOException e ) { throw new SampException( "Can't connect to " + xurl, e ); } return new StandardHubConnection( xClient, xServerFactory_, lockInfo.getSecret() ); } } /** * Returns the LockInfo which indicates how to locate the hub. * If no lockfile exists (probably becuause no appropriate hub * is running), null is returned. * The default implementation returns * LockInfo.readLockFile(getLockUrl()); * it may be overridden to provide a non-standard client profiles. * * @return hub location information * @throws IOException if the lockfile exists but cannot be read for * some reason */ public LockInfo getLockInfo() throws IOException { return LockInfo.readLockFile( getLockUrl() ); } /** * Returns the location of the Standard Profile lockfile. * By default this is the file .samp in the user's "home" * directory, unless overridden by a value of the SAMP_HUB environment * variable starting with "std-lockurl". * * @return lockfile URL */ public static URL getLockUrl() throws IOException { if ( lockUrl_ == null ) { String hublocEnv = DefaultClientProfile.HUBLOC_ENV; String hubloc = Platform.getPlatform().getEnv( hublocEnv ); final URL lockUrl; if ( hubloc != null && hubloc.startsWith( STDPROFILE_HUB_PREFIX ) ) { lockUrl = new URL( hubloc.substring( STDPROFILE_HUB_PREFIX .length() ) ); logger_.info( "Lockfile as set by env var: " + hublocEnv + "=" + hubloc ); } else if ( hubloc != null && hubloc.trim().length() > 0 ) { logger_.warning( "Ignoring non-Standard " + hublocEnv + "=" + hubloc ); lockUrl = getDefaultLockUrl(); } else { lockUrl = getDefaultLockUrl(); logger_.info( "Using default Standard Profile lockfile: " + SampUtils.urlToFile( lockUrl ) ); } lockUrl_ = lockUrl; } return lockUrl_; } /** * Returns the lockfile URL which will be used in absence of any * SAMP_HUB environment variable. * * @return URL for file .samp in user's home directory */ public static URL getDefaultLockUrl() throws IOException { if ( dfltLockUrl_ == null ) { dfltLockUrl_ = SampUtils.fileToUrl( new File( Platform.getPlatform() .getHomeDirectory(), LOCKFILE_NAME ) ); } return dfltLockUrl_; } /** * Returns an instance based on the default XML-RPC implementation. * This can be configured using system properties. * * @see XmlRpcKit#getInstance * @see org.astrogrid.samp.client.DefaultClientProfile#getProfile * @return a client profile instance */ public static StandardClientProfile getInstance() { if ( defaultInstance_ == null ) { XmlRpcKit xmlrpc = XmlRpcKit.getInstance(); defaultInstance_ = new StandardClientProfile( xmlrpc.getClientFactory(), xmlrpc.getServerFactory() ); } return defaultInstance_; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/StandardHubConnection.java000066400000000000000000000037011356450004300275710ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.IOException; import java.util.Collections; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.SampException; /** * HubConnection implementation for the Standard Profile. * * @author Mark Taylor * @since 27 Oct 2010 */ class StandardHubConnection extends XmlRpcHubConnection { private final SampXmlRpcServerFactory serverFactory_; private final String clientKey_; private CallableClientServer callableServer_; /** * Constructor. * * @param xClient XML-RPC client * @param serverFactory XML-RPC server factory implementation * @param secret samp.secret registration password */ public StandardHubConnection( SampXmlRpcClient xClient, SampXmlRpcServerFactory serverFactory, String secret ) throws SampException { super( xClient, "samp.hub.", Collections.singletonList( secret ) ); clientKey_ = getRegInfo().getPrivateKey(); serverFactory_ = serverFactory; } public Object getClientKey() { return clientKey_; } public void setCallable( CallableClient callable ) throws SampException { if ( callableServer_ == null ) { try { callableServer_ = CallableClientServer.getInstance( serverFactory_ ); } catch ( IOException e ) { throw new SampException( "Can't start client XML-RPC server", e ); } } callableServer_.addClient( this, callable ); exec( "setXmlrpcCallback", new Object[] { callableServer_.getUrl().toString() } ); } public void unregister() throws SampException { if ( callableServer_ != null ) { callableServer_.removeClient( this ); } super.unregister(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/StandardHubProfile.java000066400000000000000000000333051356450004300270750ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.URL; import java.util.ArrayList; import java.util.Date; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.DefaultClientProfile; import org.astrogrid.samp.httpd.ServerResource; import org.astrogrid.samp.httpd.UtilServer; import org.astrogrid.samp.hub.HubProfile; import org.astrogrid.samp.hub.KeyGenerator; import org.astrogrid.samp.hub.LockWriter; import org.astrogrid.samp.hub.MessageRestriction; /** * HubProfile implementation for the SAMP Standard Profile. * * @author Mark Taylor * @since 31 Jan 2011 */ public class StandardHubProfile implements HubProfile { private final SampXmlRpcClientFactory xClientFactory_; private final SampXmlRpcServerFactory xServerFactory_; private final File lockfile_; private final String secret_; private URL lockUrl_; private SampXmlRpcServer server_; private volatile HubXmlRpcHandler hubHandler_; private LockInfo lockInfo_; private static final Logger logger_ = Logger.getLogger( StandardHubProfile.class.getName() ); private static final Random random_ = KeyGenerator.createRandom(); /** * Constructs a hub profile with given configuration information. * If the supplied lockfile is null, no lockfile will * be written at hub startup. * * @param xClientFactory XML-RPC client factory implementation * @param xServerFactory XML-RPC server implementation * @param lockfile location to use for hub lockfile, or null * @param secret value for samp.secret lockfile key */ public StandardHubProfile( SampXmlRpcClientFactory xClientFactory, SampXmlRpcServerFactory xServerFactory, File lockfile, String secret ) { xClientFactory_ = xClientFactory; xServerFactory_ = xServerFactory; lockfile_ = lockfile; secret_ = secret; } /** * Constructs a hub profile with default configuration. */ public StandardHubProfile() throws IOException { this( XmlRpcKit.getInstance().getClientFactory(), XmlRpcKit.getInstance().getServerFactory(), SampUtils.urlToFile( StandardClientProfile.getLockUrl() ), createSecret() ); } public String getProfileName() { return "Standard"; } public MessageRestriction getMessageRestriction() { return null; } public synchronized void start( ClientProfile profile ) throws IOException { // Check state. if ( isRunning() ) { logger_.info( "Profile already started" ); return; } // Check for running hub. If there is a running hub, bail out. // If there is a lockfile but apparently no running hub, // continue preparations to start; the hub reference by the lockfile // may either be moribund or in the process of starting up. // To deal with the latter case, make all our preparations to give // it more time to get going before preempting it. if ( lockfile_ != null && lockfile_.exists() ) { if ( isHubAlive( xClientFactory_, lockfile_ ) ) { throw new IOException( "A hub is already running" ); } } // Start up server. try { server_ = xServerFactory_.getServer(); } catch ( IOException e ) { throw e; } catch ( Exception e ) { throw (IOException) new IOException( "Can't start XML-RPC server" ) .initCause( e ); } hubHandler_ = new HubXmlRpcHandler( xClientFactory_, profile, secret_, new KeyGenerator( "k:", 16, random_ ) ); server_.addHandler( hubHandler_ ); // Prepare lockfile information. lockInfo_ = new LockInfo( secret_, server_.getEndpoint().toString() ); lockInfo_.put( "hub.impl", profile.getClass().getName() ); lockInfo_.put( "profile.impl", this.getClass().getName() ); lockInfo_.put( "profile.start.date", new Date().toString() ); // Write lockfile information to file if required. if ( lockfile_ != null ) { // If the lockfile already exists, wait a little while in case // a hub is in the process of waking up. If it's still not // present, overwrite the lockfile with a warning. if ( ! lockfile_.createNewFile() ) { try { Thread.sleep( 500 ); } catch ( InterruptedException e ) { } if ( isHubAlive( xClientFactory_, lockfile_ ) ) { server_.removeHandler( hubHandler_ ); hubHandler_ = null; throw new IOException( "A hub is already running" ); } else { logger_.warning( "Overwriting " + lockfile_ + " lockfile " + "for apparently dead hub" ); } } FileOutputStream out = new FileOutputStream( lockfile_ ); try { writeLockInfo( lockInfo_, out ); logger_.info( "Wrote new lockfile " + lockfile_ ); try { LockWriter.setLockPermissions( lockfile_ ); logger_.info( "Lockfile permissions set to " + "user access only" ); } catch ( IOException e ) { logger_.log( Level.WARNING, "Failed attempt to change " + lockfile_ + " permissions to user access only" + " - possible security implications", e ); } } finally { try { out.close(); } catch ( IOException e ) { logger_.log( Level.WARNING, "Error closing lockfile?", e ); } } } // If the lockfile is not the default one, write a message through // the logging system. URL lockfileUrl = lockfile_ == null ? publishLockfile() : SampUtils.fileToUrl( lockfile_ ); boolean isDflt = StandardClientProfile.getDefaultLockUrl().toString() .equals( lockfileUrl.toString() ); String hubassign = DefaultClientProfile.HUBLOC_ENV + "=" + StandardClientProfile.STDPROFILE_HUB_PREFIX + lockfileUrl; logger_.log( isDflt ? Level.INFO : Level.WARNING, hubassign ); } public synchronized boolean isRunning() { return hubHandler_ != null; } public synchronized void stop() { if ( ! isRunning() ) { logger_.info( "Profile already stopped" ); return; } // Delete the lockfile if it exists and if it is the one originally // written by this runner. if ( lockInfo_ != null && lockfile_ != null ) { if ( lockfile_.exists() ) { try { LockInfo lockInfo = readLockFile( lockfile_ ); if ( lockInfo_.getSecret() .equals( lockInfo.getSecret() ) ) { assert lockInfo.equals( lockInfo_ ); boolean deleted = lockfile_.delete(); logger_.info( "Lockfile " + lockfile_ + " " + ( deleted ? "deleted" : "deletion attempt failed" ) ); } else { logger_.warning( "Lockfile " + lockfile_ + " has been " + " overwritten - not deleting" ); } } catch ( Throwable e ) { logger_.log( Level.WARNING, "Failed to delete lockfile " + lockfile_, e ); } } else { logger_.warning( "Lockfile " + lockfile_ + " has disappeared" ); } } // Withdraw service of the lockfile, if one has been published. if ( lockUrl_ != null ) { try { UtilServer.getInstance().getResourceHandler() .removeResource( lockUrl_ ); } catch ( IOException e ) { logger_.warning( "Failed to withdraw lockfile URL" ); } lockUrl_ = null; } // Remove the hub XML-RPC handler from the server. if ( hubHandler_ != null && server_ != null ) { server_.removeHandler( hubHandler_ ); } server_ = null; hubHandler_ = null; lockInfo_ = null; } /** * Returns the lockfile information associated with this object. * Only present when running. * * @return lock info */ public LockInfo getLockInfo() { return lockInfo_; } /** * Returns an HTTP URL at which the lockfile for this hub can be found. * The first call to this method causes the lockfile to be published * in this way; subsequent calls return the same value. * *

Use this with care; publishing your lockfile means that other people * can connect to your hub and potentially do disruptive things. * * @return lockfile information URL */ public URL publishLockfile() throws IOException { if ( lockUrl_ == null ) { ByteArrayOutputStream infoStrm = new ByteArrayOutputStream(); writeLockInfo( lockInfo_, infoStrm ); infoStrm.close(); final byte[] infoBuf = infoStrm.toByteArray(); URL url = UtilServer.getInstance().getResourceHandler() .addResource( "samplock", new ServerResource() { public long getContentLength() { return infoBuf.length; } public String getContentType() { return "text/plain"; } public void writeBody( OutputStream out ) throws IOException { out.write( infoBuf ); } } ); // Attempt to replace whatever host name is used by the FQDN, // for maximal usefulness to off-host clients. try { url = new URL( url.getProtocol(), InetAddress.getLocalHost() .getCanonicalHostName(), url.getPort(), url.getFile() ); } catch ( IOException e ) { } lockUrl_ = url; } return lockUrl_; } /** * Returns a string suitable for use as a Standard Profile Secret. * * @return new secret */ public static synchronized String createSecret() { return Long.toHexString( random_.nextLong() ); } /** * Attempts to determine whether a given lockfile corresponds to a hub * which is still alive. * * @param xClientFactory XML-RPC client factory implementation * @param lockfile lockfile location * @return true if the hub described at lockfile appears * to be alive and well */ private static boolean isHubAlive( SampXmlRpcClientFactory xClientFactory, File lockfile ) { LockInfo info; try { info = readLockFile( lockfile ); } catch ( Exception e ) { logger_.log( Level.WARNING, "Failed to read lockfile", e ); return false; } if ( info == null ) { return false; } URL xurl = info.getXmlrpcUrl(); if ( xurl != null ) { try { xClientFactory.createClient( xurl ) .callAndWait( "samp.hub.ping", new ArrayList() ); return true; } catch ( Exception e ) { logger_.log( Level.WARNING, "Hub ping method failed", e ); return false; } } else { logger_.warning( "No XMLRPC URL in lockfile" ); return false; } } /** * Reads lockinfo from a file. * * @param lockFile file * @return info from file */ private static LockInfo readLockFile( File lockFile ) throws IOException { return LockInfo.readLockFile( new FileInputStream( lockFile ) ); } /** * Writes lockfile information to a given output stream. * The stream is not closed. * * @param info lock info to write * @param out destination stream */ private static void writeLockInfo( LockInfo info, OutputStream out ) throws IOException { LockWriter writer = new LockWriter( out ); writer.writeComment( "SAMP Standard Profile lockfile written " + new Date() ); writer.writeComment( "Note contact URL hostname may be " + "configured using " + SampUtils.LOCALHOST_PROP + " property" ); writer.writeAssignments( info ); out.flush(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/StandardHubProfileFactory.java000066400000000000000000000044411356450004300304240ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.List; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.hub.HubProfile; import org.astrogrid.samp.hub.HubProfileFactory; /** * HubProfileFactory implementation for Standard Profile. * * @author Mark Taylor * @since 31 Jan 2011 */ public class StandardHubProfileFactory implements HubProfileFactory { private static final String secretUsage_ = "[-std:secret ]"; private static final String lockUsage_ = "[-std:httplock]"; /** * Returns "std". */ public String getName() { return "std"; } public String[] getFlagsUsage() { return new String[] { secretUsage_, lockUsage_, }; } public HubProfile createHubProfile( List flagList ) throws IOException { String secret = null; boolean httpLock = false; for ( Iterator it = flagList.iterator(); it.hasNext(); ) { String arg = (String) it.next(); if ( arg.equals( "-std:secret" ) ) { it.remove(); if ( it.hasNext() ) { secret = (String) it.next(); it.remove(); } else { throw new IllegalArgumentException( "Usage: " + secretUsage_ ); } } else if ( arg.equals( "-std:httplock" ) ) { it.remove(); httpLock = true; } else if ( arg.equals( "-std:nohttplock" ) ) { it.remove(); httpLock = false; } } File lockfile = httpLock ? null : SampUtils.urlToFile( StandardClientProfile.getLockUrl() ); XmlRpcKit xmlrpc = XmlRpcKit.getInstance(); if ( secret == null ) { secret = StandardHubProfile.createSecret(); } return new StandardHubProfile( xmlrpc.getClientFactory(), xmlrpc.getServerFactory(), lockfile, secret ); } public Class getHubProfileClass() { return StandardHubProfile.class; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/XmlRpcCallableClient.java000066400000000000000000000071021356450004300273350ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.astrogrid.samp.Message; import org.astrogrid.samp.Response; import org.astrogrid.samp.ShutdownManager; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.SampException; /** * CallableClient implementation used to communicate with XML-RPC-based * callable clients. * * @author Mark Taylor * @since 28 Jan 2011 */ class XmlRpcCallableClient implements CallableClient { private final SampXmlRpcClient xClient_; private final String privateKey_; private static volatile boolean isShutdown_; static { ShutdownManager.getInstance() .registerHook( XmlRpcCallableClient.class, ShutdownManager.PREPARE_SEQUENCE, new Runnable() { public void run() { isShutdown_ = true; } } ); } /** * Constructor. * * @param xClient XML-RPC client implementation * @param SAMP client's private key */ public XmlRpcCallableClient( SampXmlRpcClient xClient, String privateKey ) { xClient_ = xClient; privateKey_ = privateKey; } public void receiveCall( String senderId, String msgId, Message msg ) throws SampException { exec( "receiveCall", new Object[] { senderId, msgId, msg, } ); } public void receiveNotification( String senderId, Message msg ) throws SampException { exec( "receiveNotification", new Object[] { senderId, msg, } ); } public void receiveResponse( String responderId, String msgTag, Response response ) throws SampException { exec( "receiveResponse", new Object[] { responderId, msgTag, response, } ); } /** * Makes an XML-RPC call to the SAMP callable client represented * by this receiver. * * @param methodName unqualified SAMP callable client API method name * @param params array of method parameters */ private void exec( String methodName, Object[] params ) throws SampException { List paramList = new ArrayList(); paramList.add( privateKey_ ); for ( int ip = 0; ip < params.length; ip++ ) { paramList.add( params[ ip ] ); } try { rawExec( "samp.client." + methodName, paramList ); } catch ( IOException e ) { throw new SampException( e.getMessage(), e ); } } /** * Actually makes an XML-RPC call to the SAMP callable client * represented by this receiver. * * @param fqName fully qualified SAMP callable client API method name * @param paramList list of method parameters */ private void rawExec( String fqName, List paramList ) throws IOException { // In most cases, callAndForget is adequate. // However, if the JVM is in the process of shutting down, the // hub shutdown that this triggers will attempt to message clients // to tell them so, and the threads that callAndForget uses to // handle those calls will die during the communications, meaning // that the clients are not properly informed of shutdown. // So in the case of shutdown, do it synchronously. if ( isShutdown_ ) { xClient_.callAndWait( fqName, paramList ); } else { xClient_.callAndForget( fqName, paramList ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/XmlRpcHubConnection.java000066400000000000000000000213751356450004300272450ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Logger; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.ShutdownManager; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; /** * Partial HubConnection implementation based on XML-RPC. * No implementation is provided for the {@link #setCallable} method. * This is a useful base class for XML-RPC-based profile implementations, * but it is not perfectly general: some assumptions, compatible * with the Standard Profile, are made about the way that XML-RPC * calls are mapped on to SAMP hub interface calls. * * @author Mark Taylor * @author Sylvain Lafrasse * @since 16 Jul 2008 */ public abstract class XmlRpcHubConnection implements HubConnection { private final SampXmlRpcClient xClient_; private final String prefix_; private final RegInfo regInfo_; private boolean unregistered_; private static final Logger logger_ = Logger.getLogger( XmlRpcHubConnection.class.getName() ); /** * Constructor. * * @param xClient XML-RPC client * @param prefix string prepended to all hub interface method names * to turn them into XML-RPC method names * @param registerArgs arguments to the profile-specific "register" * method to initiate this connection */ public XmlRpcHubConnection( SampXmlRpcClient xClient, String prefix, List registerArgs ) throws SampException { xClient_ = xClient; prefix_ = prefix; Object regInfo = rawExec( prefix_ + "register", registerArgs ); if ( regInfo instanceof Map ) { regInfo_ = RegInfo.asRegInfo( Collections .unmodifiableMap( asMap( regInfo ) ) ); } else { throw new SampException( "Bad return value from hub register method" + " - not a map" ); } ShutdownManager.getInstance() .registerHook( this, ShutdownManager.CLIENT_SEQUENCE, new Runnable() { public void run() { finish(); } } ); } public RegInfo getRegInfo() { return regInfo_; } public void ping() throws SampException { rawExec( prefix_ + "ping", new ArrayList() ); } public void unregister() throws SampException { exec( "unregister", new Object[ 0 ] ); ShutdownManager.getInstance().unregisterHook( this ); unregistered_ = true; } public void declareMetadata( Map meta ) throws SampException { exec( "declareMetadata", new Object[] { meta } ); } public Metadata getMetadata( String clientId ) throws SampException { return Metadata .asMetadata( asMap( exec( "getMetadata", new Object[] { clientId } ) ) ); } public void declareSubscriptions( Map subs ) throws SampException { exec( "declareSubscriptions", new Object[] { subs } ); } public Subscriptions getSubscriptions( String clientId ) throws SampException { return Subscriptions .asSubscriptions( asMap( exec( "getSubscriptions", new Object[] { clientId } ) ) ); } public String[] getRegisteredClients() throws SampException { return (String[]) asList( exec( "getRegisteredClients", new Object[ 0 ] ) ) .toArray( new String[ 0 ] ); } public Map getSubscribedClients( String mtype ) throws SampException { return asMap( exec( "getSubscribedClients", new Object[] { mtype } ) ); } public void notify( String recipientId, Map msg ) throws SampException { exec( "notify", new Object[] { recipientId, msg } ); } public List notifyAll( Map msg ) throws SampException { return asList( exec( "notifyAll", new Object[] { msg } ) ); } public String call( String recipientId, String msgTag, Map msg ) throws SampException { return asString( exec( "call", new Object[] { recipientId, msgTag, msg } ) ); } public Map callAll( String msgTag, Map msg ) throws SampException { return asMap( exec( "callAll", new Object[] { msgTag, msg } ) ); } public Response callAndWait( String recipientId, Map msg, int timeout ) throws SampException { return Response .asResponse( asMap( exec( "callAndWait", new Object[] { recipientId, msg, SampUtils .encodeInt( timeout ) } ) ) ); } public void reply( String msgId, Map response ) throws SampException { exec( "reply", new Object[] { msgId, response } ); } /** * Returns an object which is used as the first argument of most * XML-RPC calls to the hub. * * @return SAMP-friendly object to identify this client */ public abstract Object getClientKey(); /** * Makes an XML-RPC call to the SAMP hub represented by this connection. * The result of {@link #getClientKey} is passed as the first argument * of the XML-RPC call. * * @param methodName unqualified SAMP hub API method name * @param params array of method parameters * @return XML-RPC call return value */ public Object exec( String methodName, Object[] params ) throws SampException { List paramList = new ArrayList(); paramList.add( getClientKey() ); for ( int ip = 0; ip < params.length; ip++ ) { paramList.add( params[ ip ] ); } return rawExec( prefix_ + methodName, paramList ); } /** * Actually makes an XML-RPC call to the SAMP hub represented by this * connection. * * @param fqName fully qualified SAMP hub API method name * @param paramList list of method parameters * @return XML-RPC call return value */ public Object rawExec( String fqName, List paramList ) throws SampException { try { return xClient_.callAndWait( fqName, paramList ); } catch ( IOException e ) { throw new SampException( e.getMessage(), e ); } } /** * Unregisters if not already unregistered. * May harmlessly be called multiple times. */ private void finish() { if ( ! unregistered_ ) { try { unregister(); } catch ( SampException e ) { } } } /** * Unregisters if not already unregistered. */ public void finalize() throws Throwable { try { finish(); } finally { super.finalize(); } } /** * Utility method to cast an object to a given SAMP-like type. * * @param obj object to cast * @param clazz class to cast to * @param name SAMP name of type * @return obj * @throws SampException if cast attempt failed */ private static Object asType( Object obj, Class clazz, String name ) throws SampException { if ( clazz.isAssignableFrom( obj.getClass() ) ) { return obj; } else { throw new SampException( "Hub returned unexpected type (" + obj.getClass().getName() + " not " + name ); } } /** * Utility method to cast an object to a string. * * @param obj object * @return object as string * @throws SampException if cast attempt failed */ private String asString( Object obj ) throws SampException { return (String) asType( obj, String.class, "string" ); } /** * Utility method to cast an object to a list. * * @param obj object * @return object as list * @throws SampException if cast attempt failed */ private List asList( Object obj ) throws SampException { return (List) asType( obj, List.class, "list" ); } /** * Utility method to cast an object to a map. * * @param obj object * @return object as map * @throws SampException if cast attempt failed */ private Map asMap( Object obj ) throws SampException { return (Map) asType( obj, Map.class, "map" ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/XmlRpcKit.java000066400000000000000000000316571356450004300252420ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.logging.Logger; /** * Encapsulates the provision of XML-RPC client and server capabilities. * Two implementations are provided in the JSAMP package; * the pluggable architecture allows others to be provided. * * @author Mark Taylor * @since 27 Aug 2008 */ public abstract class XmlRpcKit { /** Implementation based on Apache XML-RPC. */ public static final XmlRpcKit APACHE; /** Implementation which requires no external libraries. */ public static final XmlRpcKit INTERNAL; /** Internal implementation variant with verbose logging of XML I/O. */ public static final XmlRpcKit XML_LOGGING; /** Internal implementation variant with verbose logging of RPC calls. */ public static final XmlRpcKit RPC_LOGGING; /** Array of available known implementations of this class. */ public static XmlRpcKit[] KNOWN_IMPLS = { INTERNAL = createReflectionKit( "internal", "org.astrogrid.samp.xmlrpc.internal.InternalClientFactory", "org.astrogrid.samp.xmlrpc.internal.InternalServerFactory" ), XML_LOGGING = createReflectionKit( "xml-log", "org.astrogrid.samp.xmlrpc.internal" + ".XmlLoggingInternalClientFactory", "org.astrogrid.samp.xmlrpc.internal" + ".XmlLoggingInternalServerFactory" ), RPC_LOGGING = createReflectionKit( "rpc-log", "org.astrogrid.samp.xmlrpc.internal" + ".RpcLoggingInternalClientFactory", "org.astrogrid.samp.xmlrpc.internal" + ".RpcLoggingInternalServerFactory" ), APACHE = createApacheKit( "apache" ), }; /** * Property which is examined to determine which implementation to use * by default. Property values may be one of the elements of * {@link #KNOWN_IMPLS}, currently: *

    *
  • internal
  • *
  • xml-log
  • *
  • rpc-log
  • *
  • apache
  • *
* Alternatively, it may be the classname of a class which implements * {@link org.astrogrid.samp.xmlrpc.XmlRpcKit} * and has a no-arg constructor. * The property name is {@value}. */ public static final String IMPL_PROP = "jsamp.xmlrpc.impl"; private static XmlRpcKit defaultInstance_; private static Logger logger_ = Logger.getLogger( XmlRpcKit.class.getName() ); /** * Returns an XML-RPC client factory. * * @return client factory */ public abstract SampXmlRpcClientFactory getClientFactory(); /** * Returns an XML-RPC server factory. * * @return server factory */ public abstract SampXmlRpcServerFactory getServerFactory(); /** * Indicates whether this object is ready for use. * If it returns false (perhaps because some classes are unavailable * at runtime) then {@link #getClientFactory} and {@link #getServerFactory} * may throw exceptions rather than behaving as documented. * * @return true if this object works */ public abstract boolean isAvailable(); /** * Returns the name of this kit. * * @return implementation name */ public abstract String getName(); /** * Returns the default instance of this class. * What implementation this is normally depends on what classes * are present at runtime. * However, if the system property {@link #IMPL_PROP} is set this * will determine the implementation used. It may be one of: *
    *
  • apache: implementation based on the * Apache XML-RPC library
  • *
  • internal: implementation which requires no libraries * beyond JSAMP itself
  • *
  • the classname of an implementation of this class which has a * no-arg constructor
  • *
* * @return default instance of this class */ public static XmlRpcKit getInstance() { if ( defaultInstance_ == null ) { defaultInstance_ = createDefaultInstance(); logger_.info( "Default XmlRpcInstance is " + defaultInstance_ ); } return defaultInstance_; } /** * Returns an XmlRpcKit instance given its name. * * @param name name of one of the known implementations, or classname * of an XmlRpcKit implementatation with a no-arg * constructor * @return named implementation object * @throws IllegalArgumentException if none by that name can be found */ public static XmlRpcKit getInstanceByName( String name ) { // Implementation specified by system property - // try to find one with a matching name in the known list. XmlRpcKit[] impls = KNOWN_IMPLS; for ( int i = 0; i < impls.length; i++ ) { if ( name.equalsIgnoreCase( impls[ i ].getName() ) ) { return impls[ i ]; } } // Still not got one - // try to interpret system property as class name. Class clazz; try { clazz = Class.forName( name ); } catch ( ClassNotFoundException e ) { throw new IllegalArgumentException( "No such XML-RPC " + "implementation \"" + name + "\"" ); } try { return (XmlRpcKit) clazz.newInstance(); } catch ( Throwable e ) { throw (RuntimeException) new IllegalArgumentException( "Error instantiating custom " + "XmlRpcKit " + clazz.getName() ) .initCause( e ); } } /** * Constructs the default instance of this class based on system property * and class availability. * * @return XmlRpcKit object * @see #getInstance */ private static XmlRpcKit createDefaultInstance() { XmlRpcKit[] impls = KNOWN_IMPLS; String implName = System.getProperty( IMPL_PROP ); logger_.info( "Creating default XmlRpcInstance: " + IMPL_PROP + "=" + implName ); // No implementation specified by system property - // use the first one in the list that works. if ( implName == null ) { for ( int i = 0; i < impls.length; i++ ) { if ( impls[ i ].isAvailable() ) { return impls[ i ]; } } return impls[ 0 ]; } // Implementation specified by system property - // try to find one with a matching name in the known list. else { return getInstanceByName( implName ); } } /** * Returns a new XmlRpcKit given classnames for the client and server * factory classes. If the classes are not available, a kit which * returns {@link #isAvailable}()=false will be returned. * * @param name kit name * @param clientFactoryClassName name of class implementing * SampXmlRpcClientFactory which has a no-arg constructor * @param serverFactoryClassName name of class implementing * SampXmlRpcServerFactory which has a no-arg constructor * @return new XmlRpcKit constructed using reflection */ public static XmlRpcKit createReflectionKit( String name, String clientFactoryClassName, String serverFactoryClassName ) { SampXmlRpcClientFactory clientFactory = null; SampXmlRpcServerFactory serverFactory = null; Throwable error = null; try { clientFactory = (SampXmlRpcClientFactory) Class.forName( clientFactoryClassName ) .newInstance(); serverFactory = (SampXmlRpcServerFactory) Class.forName( serverFactoryClassName ) .newInstance(); } catch ( ClassNotFoundException e ) { error = e; } catch ( LinkageError e ) { error = e; } catch ( InstantiationException e ) { error = e; } catch ( IllegalAccessException e ) { error = e; } if ( clientFactory != null && serverFactory != null ) { assert error == null; return new AvailableKit( name, clientFactory, serverFactory ); } else { assert error != null; return new UnavailableKit( name, error ); } } /** * XmlRpcKit implementation which is available. */ private static class AvailableKit extends XmlRpcKit { private final String name_; private final SampXmlRpcClientFactory clientFactory_; private final SampXmlRpcServerFactory serverFactory_; /** * Constructor. * * @param name implementation name * @param clientFactory SampXmlRpcClientFactory instance * @param serverFactory SampXmlRpcServerFactory instance */ AvailableKit( String name, SampXmlRpcClientFactory clientFactory, SampXmlRpcServerFactory serverFactory ) { name_ = name; clientFactory_ = clientFactory; serverFactory_ = serverFactory; } public SampXmlRpcClientFactory getClientFactory() { return clientFactory_; } public SampXmlRpcServerFactory getServerFactory() { return serverFactory_; } public String getName() { return name_; } public boolean isAvailable() { return true; } public String toString() { return name_; } } /** * XmlRpcKit implementation which always returns false from isAvailable * and throws exceptions from getServer/Client factory methods. */ private static class UnavailableKit extends XmlRpcKit { private final String name_; private final Throwable error_; /** * Constructor. * * @param kit name * @param error the reason the kit is unavailable */ UnavailableKit( String name, Throwable error ) { name_ = name; error_ = error; } public SampXmlRpcClientFactory getClientFactory() { throw (RuntimeException) new UnsupportedOperationException( name_ + " implementation not" + " available" ) .initCause( error_ ); } public SampXmlRpcServerFactory getServerFactory() { throw (RuntimeException) new UnsupportedOperationException( name_ + " implementation not" + " available" ) .initCause( error_ ); } public String getName() { return name_; } public boolean isAvailable() { return false; } public String toString() { return name_; } } /** * Returns an available or unavailable XmlRpcKit based on Apache XML-RPC * version 1.2. * * @param name kit name * @return new kit */ private static XmlRpcKit createApacheKit( String name ) { XmlRpcKit kit = createReflectionKit( name, "org.astrogrid.samp.xmlrpc.apache.ApacheClientFactory", "org.astrogrid.samp.xmlrpc.apache.ApacheServerFactory" ); if ( kit.isAvailable() ) { try { Class xClazz = Class.forName( "org.apache.xmlrpc.XmlRpc" ); Field vField = xClazz.getField( "version" ); Object version = Modifier.isStatic( vField.getModifiers() ) ? vField.get( null ) : null; if ( version instanceof String && ((String) version) .startsWith( "Apache XML-RPC 1.2" ) ) { return kit; } else { String msg = "Wrong Apache XML-RPC version: " + version + " not 1.2"; return new UnavailableKit( name, new ClassNotFoundException( msg ) ); } } catch ( Throwable e ) { return new UnavailableKit( name, e ); } } else { return kit; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/000077500000000000000000000000001356450004300237275ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheClient.java000066400000000000000000000030661356450004300271170ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.apache; import java.io.IOException; import java.util.List; import java.util.Vector; import org.apache.xmlrpc.XmlRpcClient; import org.apache.xmlrpc.XmlRpcException; import org.astrogrid.samp.xmlrpc.SampXmlRpcClient; /** * SampXmlRpcClient implementation based on Apache XMLRPC classes. * * @author Mark Taylor * @since 16 Sep 2008 */ public class ApacheClient implements SampXmlRpcClient { private final XmlRpcClient xmlrpcClient_; /** * Constructor. * * @param xmlrpcClient Apache XML-RPC client instance */ public ApacheClient( XmlRpcClient xmlrpcClient ) { xmlrpcClient_ = xmlrpcClient; } public Object callAndWait( String method, List params ) throws IOException { try { return xmlrpcClient_ .execute( method, (Vector) ApacheUtils.toApache( params ) ); } catch ( XmlRpcException e ) { throw (IOException) new IOException( e.getMessage() ) .initCause( e ); } } public void callAndForget( String method, List params ) throws IOException { // I'm not sure that the Apache implementation is *sufficiently* // asynchronous. It does leave a thread hanging around waiting // for a response, though the result of this response is // discarded. May cause problems under heavy load. xmlrpcClient_ .executeAsync( method, (Vector) ApacheUtils.toApache( params ), null ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheClientFactory.java000066400000000000000000000011351356450004300304420ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.apache; import java.io.IOException; import java.net.URL; import org.apache.xmlrpc.XmlRpcClientLite; import org.astrogrid.samp.xmlrpc.SampXmlRpcClient; import org.astrogrid.samp.xmlrpc.SampXmlRpcClientFactory; /** * SampXmlRpcClientFactory implementation based on Apache XMLRPC classes. * * @author Mark Taylor * @since 16 Sep 2008 */ public class ApacheClientFactory implements SampXmlRpcClientFactory { public SampXmlRpcClient createClient( URL endpoint ) throws IOException { return new ApacheClient( new XmlRpcClientLite( endpoint ) ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheServer.java000066400000000000000000000125761356450004300271550ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.apache; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Vector; import org.apache.xmlrpc.WebServer; import org.apache.xmlrpc.XmlRpcHandler; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.xmlrpc.SampXmlRpcServer; import org.astrogrid.samp.xmlrpc.SampXmlRpcHandler; /** * SampXmlRpcServer implementation based on Apache XML-RPC library. * * @author Mark Taylor * @since 22 Aug 2008 */ public class ApacheServer implements SampXmlRpcServer { private final WebServer webServer_; private final URL endpoint_; private final List handlerList_; /** * Private constructor used by all other constructors. * Uses the private LabelledServer class to aggregate the required * information. * * @param server server with metadata */ private ApacheServer( LabelledServer server ) { webServer_ = server.webServer_; endpoint_ = server.endpoint_; handlerList_ = Collections.synchronizedList( new ArrayList() ); webServer_.addHandler( "$default", new XmlRpcHandler() { public Object execute( String method, Vector params ) throws Exception { return doExecute( method, params ); } } ); } /** * Constructs a new server based on a given WebServer object. * Responsibility for starting the WebServer and performing * any other required configuration lies with the caller. * * @param webServer apache xmlrpc webserver object * @param port port number on which the server is running */ public ApacheServer( WebServer webServer, int port ) { this( new LabelledServer( webServer, getServerEndpoint( port ) ) ); } /** * Constructs a new server starting up a new WebServer object. * The server runs in a daemon thread. */ public ApacheServer() throws IOException { this( createLabelledServer( true ) ); webServer_.start(); } public URL getEndpoint() { return endpoint_; } public void addHandler( SampXmlRpcHandler handler ) { handlerList_.add( handler ); } public void removeHandler( SampXmlRpcHandler handler ) { handlerList_.remove( handler ); } /** * Does the work for executing an XML-RPC request. * * @param fqMethod fully qualified XML-RPC method name * @param paramVec Apache-style list of method parameters */ private Object doExecute( String fqMethod, Vector paramVec ) throws Exception { SampXmlRpcHandler[] handlers = (SampXmlRpcHandler[]) handlerList_.toArray( new SampXmlRpcHandler[ 0 ] ); for ( int ih = 0; ih < handlers.length; ih++ ) { SampXmlRpcHandler handler = handlers[ ih ]; if ( handler.canHandleCall( fqMethod ) ) { List paramList = (List) ApacheUtils.fromApache( paramVec ); Object result = handler.handleCall( fqMethod, paramList, null ); return ApacheUtils.toApache( result ); } } throw new UnsupportedOperationException( "No handler for method " + fqMethod ); } /** * Constructs a new LabelledServer object suitable for use with this * server. * * @param isDaemon whether the WebServer's main thread should run * in daemon mode */ private static LabelledServer createLabelledServer( final boolean isDaemon ) throws IOException { int port = SampUtils.getUnusedPort( 2300 ); WebServer server = new WebServer( port ) { // Same as superclass implementation except that the listener // thread is marked as a daemon. public void start() { if ( this.listener == null ) { this.listener = new Thread( this, "XML-RPC Weblistener" ); this.listener.setDaemon( isDaemon ); this.listener.start(); } } }; return new LabelledServer( server, getServerEndpoint( port ) ); } /** * Returns the endpoint URL to use for an Apache server running on a * given port. * * @param port port number * @return URL */ private static URL getServerEndpoint( int port ) { String endpoint = "http://" + SampUtils.getLocalhost() + ":" + port + "/"; try { return new URL( endpoint ); } catch ( MalformedURLException e ) { throw (Error) new AssertionError( "Bad protocol http?? " + endpoint ) .initCause( e ); } } /** * Convenience class which aggregates a WebServer and an endpoint. */ private static class LabelledServer { private final WebServer webServer_; private final URL endpoint_; /** * Constructor. * * @param webServer web server * @param endpoint URL */ LabelledServer( WebServer webServer, URL endpoint ) { webServer_ = webServer; endpoint_ = endpoint; } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheServerFactory.java000066400000000000000000000012551356450004300304750ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.apache; import java.io.IOException; import org.astrogrid.samp.xmlrpc.SampXmlRpcServer; import org.astrogrid.samp.xmlrpc.SampXmlRpcServerFactory; /** * SampXmlRpcServerFactory implementation which uses Apache classes. * Server construction is lazy and the same server is returned each time. * * @author Mark Taylor * @since 22 Aug 2008 */ public class ApacheServerFactory implements SampXmlRpcServerFactory { private SampXmlRpcServer server_; public synchronized SampXmlRpcServer getServer() throws IOException { if ( server_ == null ) { server_ = new ApacheServer(); } return server_; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/ApacheUtils.java000066400000000000000000000044451356450004300270030ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.apache; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; /** * Provides utility methods to perform translations between the * data structurs used by the org.apache.xmlrpc classes and those used * by JSAMP. * * @author Mark Taylor * @since 22 Aug 2008 */ class ApacheUtils { /** * Private constructor prevents instantiation. */ private ApacheUtils() { } /** * Converts an object from JSAMP XML-RPC form to Apache XML-RPC form. * Basically, this means converting * {@link java.util.Map}s to {@link java.util.Hashtable}s and * {@link java.util.List}s to {@link java.util.Vector}s. * * @param obj XML-RPC data structure suitable for use within JSAMP * @return XML-RPC data structure suitable for use within Apache */ public static Object toApache( Object obj ) { if ( obj instanceof List ) { Vector vec = new Vector(); for ( Iterator it = ((List) obj).iterator(); it.hasNext(); ) { vec.add( toApache( it.next() ) ); } return vec; } else if ( obj instanceof Map ) { Hashtable hash = new Hashtable(); for ( Iterator it = ((Map) obj).entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); hash.put( entry.getKey(), toApache( entry.getValue() ) ); } return hash; } else if ( obj instanceof String ) { return obj; } else { throw new IllegalArgumentException( "Non-SAMP object type " + ( obj == null ? null : obj.getClass().getName() ) ); } } /** * Converts an object from Apache XML-RPC form to JSAMP XML-RPC form. * Since Hashtable implements Map and Vector implements List, this is * a no-op. * * @param data XML-RPC data structure suitable for use within Apache * @return XML-RPC data structure suitable for use within JSAMP */ public static Object fromApache( Object data ) { return data; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/apache/package.html000066400000000000000000000003151356450004300262070ustar00rootroot00000000000000 Implementation of pluggable XML-RPC layer using Apache XML-RPC. These classes were developed against the apache-1.2-b1 version; they may or may not work with other versions of that library. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/000077500000000000000000000000001356450004300243225ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/InternalClient.java000066400000000000000000000172731356450004300301120ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.xmlrpc.SampXmlRpcClient; /** * XML-RPC client implementation suitable for use with SAMP. * This implementation is completely freestanding and requires no other * libraries. * * @author Mark Taylor * @since 26 Aug 2008 */ public class InternalClient implements SampXmlRpcClient { private final URL endpoint_; private final String userAgent_; private static final Logger logger_ = Logger.getLogger( InternalClient.class.getName() ); /** * Constructor. * * @param endpoint endpoint */ public InternalClient( URL endpoint ) { endpoint_ = endpoint; userAgent_ = "JSAMP/" + SampUtils.getSoftwareVersion(); } public Object callAndWait( String method, List params ) throws IOException { HttpURLConnection connection = (HttpURLConnection) endpoint_.openConnection(); byte[] callBuf = serializeCall( method, params ); connection.setDoOutput( true ); connection.setDoInput( true ); connection.setRequestMethod( "POST" ); connection.setRequestProperty( "Content-Type", "text/xml" ); connection.setRequestProperty( "Content-Length", Integer.toString( callBuf.length ) ); connection.setRequestProperty( "User-Agent", userAgent_ ); connection.connect(); OutputStream out = connection.getOutputStream(); out.write( callBuf ); out.flush(); out.close(); int responseCode = connection.getResponseCode(); if ( responseCode != HttpURLConnection.HTTP_OK ) { throw new IOException( responseCode + " " + connection.getResponseMessage() ); } InputStream in = new BufferedInputStream( connection.getInputStream() ); Object result = deserializeResponse( in ); connection.disconnect(); return result; } // NOTE: if this method is invoked from a shutdownHook thread, // the call may not complete because it is completed from a new thread. public void callAndForget( String method, List params ) throws IOException { final HttpURLConnection connection = (HttpURLConnection) endpoint_.openConnection(); byte[] callBuf = serializeCall( method, params ); connection.setDoOutput( true ); connection.setDoInput( true ); connection.setRequestMethod( "POST" ); connection.setRequestProperty( "Content-Type", "text/xml" ); connection.setRequestProperty( "Content-Length", Integer.toString( callBuf.length ) ); connection.setRequestProperty( "User-Agent", userAgent_ ); connection.connect(); OutputStream out = connection.getOutputStream(); out.write( callBuf ); out.flush(); out.close(); // It would be nice to just not read the input stream at all. // However, connection.setDoInput(false) and doing no reads causes // trouble - probably the call doesn't complete at the other end or // something. So read it to the end asynchronously. new Thread() { public void run() { try { InputStream in = new BufferedInputStream( connection.getInputStream() ); while ( in.read() >= 0 ) {} int responseCode = connection.getResponseCode(); if ( responseCode != HttpURLConnection.HTTP_OK ) { logger_.warning( responseCode + " " + connection.getResponseMessage() ); } } catch ( IOException e ) { } finally { connection.disconnect(); } } }.start(); } /** * Generates the XML methodCall document corresponding * to an XML-RPC method call. * * @param method methodName string * @param paramList list of XML-RPC parameters * @return XML document as byte array */ protected byte[] serializeCall( String method, List paramList ) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); XmlWriter xout = new XmlWriter( bos, 2 ); xout.start( "methodCall" ); xout.inline( "methodName", method ); if ( ! paramList.isEmpty() ) { xout.start( "params" ); for ( Iterator it = paramList.iterator(); it.hasNext(); ) { xout.start( "param" ); xout.sampValue( it.next() ); xout.end( "param" ); } xout.end( "params" ); } xout.end( "methodCall" ); xout.close(); return bos.toByteArray(); } /** * Deserializes an XML-RPC methodResponse document to a * Java object. * * @param in input stream containing response document */ protected Object deserializeResponse( InputStream in ) throws IOException { try { Document doc = XmlUtils.createDocumentBuilder().parse( in ); Element top = XmlUtils.getChild( XmlUtils.getChild( doc, "methodResponse" ) ); String topName = top.getTagName(); if ( "fault".equals( topName ) ) { Element value = XmlUtils.getChild( top, "value" ); XmlUtils.getChild( value, "struct" ); Map faultMap = (Map) XmlUtils.parseSampValue( value ); Object fcode = faultMap.get( "faultCode" ); Object fmsg = faultMap.get( "faultString" ); int code = fcode instanceof Integer ? ((Integer) fcode).intValue() : -9999; final String msg = String.valueOf( fmsg ); throw new XmlRpcFault( code, msg ); } else if ( "params".equals( topName ) ) { Element value = XmlUtils.getChild( XmlUtils.getChild( top, "param" ), "value" ); return XmlUtils.parseSampValue( value ); } else { throw new XmlRpcFormatException( "Not or ?" ); } } catch ( ParserConfigurationException e ) { throw (IOException) new IOException( "Trouble with XML parsing" ) .initCause( e ); } catch ( SAXException e ) { throw (IOException) new IOException( "Trouble with XML parsing" ) .initCause( e ); } catch ( DOMException e ) { throw (IOException) new IOException( "Trouble with XML parsing" ) .initCause( e ); } } /** * IOException representing an incoming XML-RPC fault. */ private static class XmlRpcFault extends IOException { public XmlRpcFault( int code, String msg ) { super( "XML-RPC Fault (" + code + ": " + msg + ")" ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/InternalClientFactory.java000066400000000000000000000011051356450004300314250ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; import java.net.URL; import org.astrogrid.samp.xmlrpc.SampXmlRpcClient; import org.astrogrid.samp.xmlrpc.SampXmlRpcClientFactory; /** * Freestanding SampXmlRpcClientFactory implementation. * This implementation requires no external libraries. * * @author Mark Taylor * @since 16 Sep 2008 */ public class InternalClientFactory implements SampXmlRpcClientFactory { public SampXmlRpcClient createClient( URL endpoint ) throws IOException { return new InternalClient( endpoint ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/InternalServer.java000066400000000000000000000275031356450004300301370ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.ServerSocket; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.httpd.HttpServer; import org.astrogrid.samp.httpd.UtilServer; import org.astrogrid.samp.xmlrpc.SampXmlRpcHandler; import org.astrogrid.samp.xmlrpc.SampXmlRpcServer; import org.w3c.dom.Document; /** * SampXmlRpcServer implementation without external dependencies. * The reqInfo argument passed to the * {@link SampXmlRpcHandler#handleCall handleCall} method of registered * SampXmlRpcHandlers is the associated * {@link org.astrogrid.samp.httpd.HttpServer.Request}. * * @author Mark Taylor * @since 27 Aug 2008 */ public class InternalServer implements SampXmlRpcServer { private final HttpServer server_; private final URL endpoint_; private final List handlerList_; private final HttpServer.Handler serverHandler_; private static final HttpServer.Response GET_RESPONSE = createInfoResponse( true ); private static final HttpServer.Response HEAD_RESPONSE = createInfoResponse( false ); private static final Logger logger_ = Logger.getLogger( InternalServer.class.getName() ); /** * Constructor based on a given HTTP server. * It is the caller's responsibility to configure and start the HttpServer. * * @param httpServer server for processing HTTP requests * @param path path part of server endpoint (starts with "/"); */ public InternalServer( HttpServer httpServer, final String path ) throws IOException { server_ = httpServer; endpoint_ = new URL( server_.getBaseUrl(), path ); handlerList_ = Collections.synchronizedList( new ArrayList() ); serverHandler_ = new HttpServer.Handler() { public HttpServer.Response serveRequest( HttpServer.Request req ) { if ( req.getUrl().equals( path ) ) { String method = req.getMethod(); if ( "POST".equals( method ) ) { return getXmlRpcResponse( req ); } else if ( "GET".equals( method ) ) { return GET_RESPONSE; } else if ( "HEAD".equals( method ) ) { return HEAD_RESPONSE; } else { return HttpServer .create405Response( new String[] { "POST", "GET", "HEAD", } ); } } else { return null; } } }; } /** * Constructs a server running with default characteristics. * Currently, the default server * {@link org.astrogrid.samp.httpd.UtilServer#getInstance} is used. */ public InternalServer() throws IOException { this( UtilServer.getInstance().getServer(), UtilServer.getInstance().getBasePath( "/xmlrpc" ) ); } public URL getEndpoint() { return endpoint_; } /** * Returns the HTTP server hosting this XML-RPC server. * * @return http server */ public HttpServer getHttpServer() { return server_; } public void addHandler( SampXmlRpcHandler handler ) { synchronized ( handlerList_ ) { if ( handlerList_.isEmpty() ) { server_.addHandler( serverHandler_ ); } handlerList_.add( handler ); } } public void removeHandler( SampXmlRpcHandler handler ) { synchronized ( handlerList_ ) { handlerList_.remove( handler ); if ( handlerList_.isEmpty() ) { server_.removeHandler( serverHandler_ ); } } } /** * Returns the HTTP response object given an incoming XML-RPC POST request. * Any error should be handled by returning a fault-type methodResponse * element rather than by throwing an exception. * * @param request POSTed HTTP request * @return XML-RPC response (possibly fault) */ protected HttpServer.Response getXmlRpcResponse( HttpServer.Request request ) { byte[] rbuf; try { rbuf = getResultBytes( getXmlRpcResult( request ) ); } catch ( Throwable e ) { boolean isSerious = e instanceof Error; logger_.log( isSerious ? Level.WARNING : Level.INFO, "XML-RPC fault return", e ); try { rbuf = getFaultBytes( e ); } catch ( IOException e2 ) { return HttpServer.createErrorResponse( 500, "Server error", e2 ); } } final byte[] replyBuf = rbuf; Map hdrMap = new LinkedHashMap(); hdrMap.put( "Content-Length", Integer.toString( replyBuf.length ) ); hdrMap.put( "Content-Type", "text/xml" ); return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) throws IOException { out.write( replyBuf ); } }; } /** * Returns the SAMP-friendly (string, list and map only) object representing * the reply to an XML-RPC request given by a request. * * @param request POSTed HTTP request * @return SAMP-friendly object * @throws Exception in case of error (will become XML-RPC fault) */ private Object getXmlRpcResult( HttpServer.Request request ) throws Exception { byte[] body = request.getBody(); // Parse body as XML document. if ( body == null || body.length == 0 ) { throw new XmlRpcFormatException( "No body in POSTed request" ); } Document doc = XmlUtils.createDocumentBuilder() .parse( new ByteArrayInputStream( body ) ); // Extract XML-RPC information from DOM. XmlRpcCall call = XmlRpcCall.createCall( doc ); String methodName = call.getMethodName(); List paramList = call.getParams(); // Find one of the registered handlers to handle this request. SampXmlRpcHandler handler = null; SampXmlRpcHandler[] handlers = (SampXmlRpcHandler[]) handlerList_.toArray( new SampXmlRpcHandler[ 0 ] ); for ( int ih = 0; ih < handlers.length && handler == null; ih++ ) { SampXmlRpcHandler h = handlers[ ih ]; if ( h.canHandleCall( methodName ) ) { handler = h; } } if ( handler == null ) { throw new XmlRpcFormatException( "Unknown XML-RPC method " + methodName ); } // Pass the call to the handler and return the result. return handleCall( handler, methodName, paramList, request ); } /** * Actually passes the XML-RPC method name and parameter list to one * of the registered servers for processing. * * @param handler handler which has declared it can handle the * named method * @param methodName XML-RPC method name * @param paramList list of parameters to XML-RPC call * @param request HTTP request from which this call originated */ protected Object handleCall( SampXmlRpcHandler handler, String methodName, List paramList, HttpServer.Request request ) throws Exception { return handler.handleCall( methodName, paramList, request ); } /** * Turns a SAMP-friendly (string, list, map only) object into an array * of bytes giving an XML-RPC methodResponse document. * * @param result SAMP-friendly object * @return XML methodResponse document as byte array */ public static byte[] getResultBytes( Object result ) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); BufferedOutputStream bout = new BufferedOutputStream( out ); XmlWriter xout = new XmlWriter( bout, 2 ); xout.start( "methodResponse" ); xout.start( "params" ); xout.start( "param" ); xout.sampValue( result ); xout.end( "param" ); xout.end( "params" ); xout.end( "methodResponse" ); xout.close(); return out.toByteArray(); } /** * Turns an exception into an array of bytes giving an XML-RPC * methodResponse (fault) document. * * @param error throwable * @return XML methodResponse document as byte array */ public static byte[] getFaultBytes( Throwable error ) throws IOException { int faultCode = 1; String faultString = error.toString(); // Write the method response element. We can't use the XmlWriter // sampValue method to do the grunt-work here since the faultCode // contains an , which is not a known SAMP type. ByteArrayOutputStream out = new ByteArrayOutputStream(); BufferedOutputStream bout = new BufferedOutputStream( out ); XmlWriter xout = new XmlWriter( bout, 2 ); xout.start( "methodResponse" ); xout.start( "fault" ); xout.start( "value" ); xout.start( "struct" ); xout.start( "member" ); xout.inline( "name", "faultCode" ); xout.start( "value" ); xout.inline( "int", Integer.toString( faultCode ) ); xout.end( "value" ); xout.end( "member" ); xout.start( "member" ); xout.inline( "name", "faultString" ); xout.inline( "value", faultString ); xout.end( "member" ); xout.end( "struct" ); xout.end( "value" ); xout.end( "fault" ); xout.end( "methodResponse" ); xout.close(); return out.toByteArray(); } /** * Returns a simple response suitable for GET/HEAD at the XML-RPC * server's endpoint. * * @param withData true for text (GET), false for no text (HEAD) * @return HTTP response */ private static HttpServer.Response createInfoResponse( final boolean withData ) { String text = new StringBuffer() .append( "\n" ) .append( "XML-RPC\n" ) .append( "\n" ) .append( "

XML-RPC Server

\n" ) .append( "

This is an " ) .append( "XML-RPC server.\n" ) .append( "

\n" ) .append( "

Try POSTing.

\n" ) .append( "\n" ) .append( "\n" ) .toString(); byte[] buf1; try { buf1 = text.getBytes( "utf-8" ); } catch ( UnsupportedEncodingException e ) { assert false : "no UTF-8??"; buf1 = new byte[ 0 ]; } final byte[] buf = buf1; Map hdrMap = new LinkedHashMap(); hdrMap.put( "Content-Type", "text/html" ); hdrMap.put( "Content-Length", Integer.toString( buf.length ) ); return new HttpServer.Response( 200, "OK", hdrMap ) { public void writeBody( OutputStream out ) throws IOException { if ( withData ) { out.write( buf ); } } }; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/InternalServerFactory.java000066400000000000000000000011211356450004300314530ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; import org.astrogrid.samp.xmlrpc.SampXmlRpcServer; import org.astrogrid.samp.xmlrpc.SampXmlRpcServerFactory; /** * Freestanding SampXmlRpcServerFactory implementation. * A new server object is returned each time, but this does not * mean a new port opened each time. * * @author Mark Taylor * @since 26 Aug 2008 */ public class InternalServerFactory implements SampXmlRpcServerFactory { public synchronized SampXmlRpcServer getServer() throws IOException { return new InternalServer(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/RpcLoggingInternalClient.java000066400000000000000000000036101356450004300320540ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.net.URL; import java.util.List; import java.util.Map; import org.astrogrid.samp.SampUtils; /** * InternalClient subclass which additionally logs all XML-RPC calls/responses * to an output stream. * * @author Mark Taylor * @since 2 Dec 2008 */ public class RpcLoggingInternalClient extends InternalClient { private final PrintStream out_; /** * Constructor. * * @param endpoint endpoint * @param out logging output stream */ public RpcLoggingInternalClient( URL endpoint, PrintStream out ) { super( endpoint ); out_ = out; } protected byte[] serializeCall( String method, List paramList ) throws IOException { String paramString = SampUtils.formatObject( paramList, 2 ); synchronized ( out_ ) { out_.println( "CLIENT OUT:" ); out_.println( method ); out_.println( paramString ); out_.println(); } return super.serializeCall( method, paramList ); } protected Object deserializeResponse( InputStream in ) throws IOException { Object response = super.deserializeResponse( in ); if ( response == null || response instanceof String && ((String) response).length() == 0 || response instanceof Map && ((Map) response).isEmpty() || response instanceof List && ((List) response).isEmpty() ) { // treat as no response } else { String responseString = SampUtils.formatObject( response, 2 ); synchronized ( out_ ) { out_.println( "CLIENT IN:" ); out_.println( responseString ); out_.println(); } } return response; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/RpcLoggingInternalClientFactory.java000066400000000000000000000011501356450004300334010ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; import java.net.URL; import org.astrogrid.samp.xmlrpc.SampXmlRpcClient; import org.astrogrid.samp.xmlrpc.SampXmlRpcClientFactory; /** * Freestanding ClientFactory implementation which logs all XML-RPC * calls/responses to standard output. * * @author Mark Taylor * @since 2 Dec 2008 */ public class RpcLoggingInternalClientFactory implements SampXmlRpcClientFactory { public SampXmlRpcClient createClient( URL endpoint ) throws IOException { return new RpcLoggingInternalClient( endpoint, System.out ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/RpcLoggingInternalServer.java000066400000000000000000000052411356450004300321060ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; import java.io.PrintStream; import java.util.List; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.httpd.HttpServer; import org.astrogrid.samp.xmlrpc.SampXmlRpcHandler; /** * InternalServer subclass which additionally logs all XML-RPC calls/responses * to an output stream. * * @author Mark Taylor * @since 2 Dec 2008 */ public class RpcLoggingInternalServer extends InternalServer { private final PrintStream out_; /** * Constructor based on a given HTTP server. * It is the caller's responsibility to configure and start the HttpServer. * * @param server server for processing HTTP requests * @param path path part of server endpoint (starts with "/"); * @param out output stream for logging */ public RpcLoggingInternalServer( HttpServer server, String path, PrintStream out ) throws IOException { super( server, path ); out_ = out; } /** * Constructs a server running with default characteristics * on any free port. The server is started as a daemon thread. * * @param out output stream for logging */ public RpcLoggingInternalServer( PrintStream out ) throws IOException { super(); out_ = out; } protected Object handleCall( SampXmlRpcHandler handler, String methodName, List paramList, HttpServer.Request request ) throws Exception { String paramString = SampUtils.formatObject( paramList, 2 ); synchronized ( out_ ) { out_.println( "SERVER IN:" ); out_.println( methodName ); out_.println( paramString ); out_.println(); } final Object result; try { result = super.handleCall( handler, methodName, paramList, request ); } catch ( Throwable e ) { synchronized ( out_ ) { out_.println( "SERVER ERROR:" ); out_.println( methodName ); e.printStackTrace( out_ ); out_.println(); } if ( e instanceof Error ) { throw (Error) e; } else { throw (Exception) e; } } String resultString = SampUtils.formatObject( result, 2 ); synchronized ( out_ ) { out_.println( "SERVER OUT:" ); out_.println( methodName ); out_.println( resultString ); out_.println(); } return result; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/RpcLoggingInternalServerFactory.java000066400000000000000000000012771356450004300334430ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; import org.astrogrid.samp.xmlrpc.SampXmlRpcServer; import org.astrogrid.samp.xmlrpc.SampXmlRpcServerFactory; /** * Freestanding ServerFactory implementation which logs all XML-RPC * calls/responses to standard output. * * @author Mark Taylor * @since 2 Dec 2008 */ public class RpcLoggingInternalServerFactory implements SampXmlRpcServerFactory { private RpcLoggingInternalServer server_; public synchronized SampXmlRpcServer getServer() throws IOException { if ( server_ == null ) { server_ = new RpcLoggingInternalServer( System.out ); } return server_; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlLoggingInternalClient.java000066400000000000000000000033361356450004300320750ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.util.List; import java.net.URL; /** * InternalClient subclass which additionally logs all XML-RPC calls/responses * to an output stream. * * @author Mark Taylor * @since 2 Dec 2008 */ public class XmlLoggingInternalClient extends InternalClient { private final PrintStream out_; /** * Constructor. * * @param endpoint endpoint * @param out output stream for logging */ public XmlLoggingInternalClient( URL endpoint, PrintStream out ) { super( endpoint ); out_ = out; } protected byte[] serializeCall( String method, List paramList ) throws IOException { byte[] buf = super.serializeCall( method, paramList ); synchronized ( out_ ) { out_.println( "CLIENT OUT:" ); out_.write( buf ); out_.println(); } return buf; } protected Object deserializeResponse( InputStream in ) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); byte[] buf = new byte[ 1024 ]; for ( int nb; ( nb = in.read( buf ) ) >= 0; ) { bout.write( buf, 0, nb ); } byte[] obuf = bout.toByteArray(); synchronized ( out_ ) { out_.println( "CLIENT IN:" ); out_.write( obuf ); out_.println(); } InputStream copyIn = new ByteArrayInputStream( bout.toByteArray() ); return super.deserializeResponse( copyIn ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlLoggingInternalClientFactory.java000066400000000000000000000011371356450004300334220ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; import java.net.URL; import org.astrogrid.samp.xmlrpc.SampXmlRpcClient; import org.astrogrid.samp.xmlrpc.SampXmlRpcClientFactory; /** * Freestanding ClientFactory implementation which logs all incoming * and outgoing HTTP data. * * @author Mark Taylor * @since 2 Dec 2008 */ public class XmlLoggingInternalClientFactory implements SampXmlRpcClientFactory { public SampXmlRpcClient createClient( URL endpoint ) throws IOException { return new XmlLoggingInternalClient( endpoint, System.out ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlLoggingInternalServer.java000066400000000000000000000047061356450004300321270ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import org.astrogrid.samp.httpd.HttpServer; /** * Freestanding InternalServer implementation which logs all incoming * and outgoing HTTP data. * * @author Mark Taylor * @since 2 Dec 2008 */ public class XmlLoggingInternalServer extends InternalServer { private final PrintStream out_; /** * Constructor based on a given HTTP server. * It is the caller's responsibility to configure and start the HttpServer. * * @param server server for processing HTTP requests * @param path path part of server endpoint (starts with "/"); * @param out output stream for loggging */ public XmlLoggingInternalServer( HttpServer server, String path, PrintStream out ) throws IOException { super( server, path ); out_ = out; } /** * Constructs a server running with default characteristics * on any free port. The server is started as a daemon thread. * * @param out output stream for loggging */ public XmlLoggingInternalServer( PrintStream out ) throws IOException { super(); out_ = out; } protected HttpServer.Response getXmlRpcResponse( HttpServer.Request request ) { synchronized ( out_ ) { out_.println( "SERVER IN:" ); try { out_.write( request.getBody() ); } catch ( IOException e ) { } out_.println(); } return new LoggingResponse( super.getXmlRpcResponse( request ) ); } private class LoggingResponse extends HttpServer.Response { final HttpServer.Response base_; LoggingResponse( HttpServer.Response base ) { super( base.getStatusCode(), base.getStatusPhrase(), base.getHeaderMap() ); base_ = base; } public void writeBody( OutputStream out ) throws IOException { ByteArrayOutputStream bout = new ByteArrayOutputStream(); base_.writeBody( bout ); byte[] bbuf = bout.toByteArray(); synchronized ( out_ ) { out_.println( "SERVER OUT:" ); out_.write( bbuf ); out_.println(); } out.write( bbuf ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlLoggingInternalServerFactory.java000066400000000000000000000012641356450004300334530ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; import org.astrogrid.samp.xmlrpc.SampXmlRpcServer; import org.astrogrid.samp.xmlrpc.SampXmlRpcServerFactory; /** * Freestanding ServerFactory implementation which logs all incoming * and outgoing HTTP data. * * @author Mark Taylor * @since 2 Dec 2008 */ public class XmlLoggingInternalServerFactory implements SampXmlRpcServerFactory { private XmlLoggingInternalServer server_; public synchronized SampXmlRpcServer getServer() throws IOException { if ( server_ == null ) { server_ = new XmlLoggingInternalServer( System.out ); } return server_; } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlRpcCall.java000066400000000000000000000064561356450004300272010ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.w3c.dom.Document; import org.w3c.dom.Element; /** * Represents the content of an XML-RPC methodCall element. * * @author Mark Taylor * @since 11 Mar 2016 * @see XML-RPC */ public class XmlRpcCall { private final String methodName_; private final List params_; /** * Constructor. * * @param methodName content of methodName element * @param params SAMP-friendly list of parameters as contained * in the params element */ public XmlRpcCall( String methodName, List params ) { methodName_ = methodName; params_ = Collections.unmodifiableList( params ); } /** * Returns the method name. * * @return content of methodName element */ public String getMethodName() { return methodName_; } /** * Returns the parameter list. * * @return SAMP-friendly list of parameter values from * params element */ public List getParams() { return params_; } /** * Constructs an XmlRpcCall instance from a document with a * methodCall element at top level. * * @param callDoc node whose child is an XML-RPC * methodCall element * @return call instance * @throws XmlRpcFormatException if the document does not have the * expected form */ public static XmlRpcCall createCall( Document callDoc ) throws XmlRpcFormatException { // Get expected top-level element. Element callEl = XmlUtils.getChild( callDoc, "methodCall" ); // Get methodName and params elements. String methodName = null; Element paramsEl = null; Element[] methodChildren = XmlUtils.getChildren( callEl ); for ( int i = 0; i < methodChildren.length; i++ ) { Element el = methodChildren[ i ]; String tagName = el.getTagName(); if ( tagName.equals( "methodName" ) ) { methodName = XmlUtils.getTextContent( el ); } else if ( tagName.equals( "params" ) ) { paramsEl = el; } } if ( methodName == null ) { throw new XmlRpcFormatException( "No methodName element" ); } // Extract parameter values as list. Element[] paramEls = paramsEl == null ? new Element[ 0 ] : XmlUtils.getChildren( paramsEl ); int np = paramEls.length; List paramList = new ArrayList( np ); for ( int i = 0; i < np; i++ ) { Element paramEl = paramEls[ i ]; if ( ! "param".equals( paramEl.getTagName() ) ) { throw new XmlRpcFormatException( "Non-param child of params" ); } else { Element valueEl = XmlUtils.getChild( paramEl, "value" ); paramList.add( XmlUtils.parseSampValue( valueEl ) ); } } // Construct and return call. return new XmlRpcCall( methodName, paramList ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlRpcFormatException.java000066400000000000000000000011641356450004300314240ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; /** * Exception thrown when an XML document which is intended for XML-RPC * processing has the wrong format, for instance violates the XML-RPC spec. * * @author Mark Taylor * @since 26 Aug 2008 */ class XmlRpcFormatException extends IOException { /** * No-arg constructor. */ public XmlRpcFormatException() { this( "Badly-formed XML-RPC request/response" ); } /** * Constructor. * * @param msg message */ public XmlRpcFormatException( String msg ) { super( msg ); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlUtils.java000066400000000000000000000211161356450004300267470ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; /** * Utilities for XML manipulations required by SAMP/XML-RPC. * * @author Mark Taylor * @since 26 Aug 2008 */ public class XmlUtils { private static Logger logger_ = Logger.getLogger( XmlUtils.class.getName() ); private static DocumentBuilderFactory dbFact_; /** * Private constructor prevents instantiation. */ private XmlUtils() { } /** * Returns an array of all the Element children of a DOM node. * * @param parent parent node * @return children array */ public static Element[] getChildren( Node parent ) { NodeList nodeList = parent.getChildNodes(); int nnode = nodeList.getLength(); List elList = new ArrayList( nnode ); for ( int i = 0; i < nnode; i++ ) { Node node = nodeList.item( i ); if ( node instanceof Element ) { elList.add( (Element) node ); } } return (Element[]) elList.toArray( new Element[ 0 ] ); } /** * Returns the single element child of a DOM node. * * @param parent parent node * @return sole child element * @throws XmlRpcFormatException if there is not exactly one child * per element */ public static Element getChild( Node parent ) throws XmlRpcFormatException { Element[] els = getChildren( parent ); if ( els.length == 1 ) { return els[ 0 ]; } else if ( els.length == 0 ) { throw new XmlRpcFormatException( "No child element of " + ((Element) parent).getTagName() ); } else { throw new XmlRpcFormatException( "Multiple children of " + ((Element) parent).getTagName() ); } } /** * Returns the single child element of a DOM node, which has a given * known name. * * @param parent parent node * @param tagName child node name * @return sole child element with name tagName * @throws XmlRpcFormatException if there is not exactly one child * element or if it does not have name tagName */ public static Element getChild( Node parent, String tagName ) throws XmlRpcFormatException { Element child = getChild( parent ); if ( ! tagName.equals( child.getTagName() ) ) { throw new XmlRpcFormatException( "Unexpected child of " + ((Element) parent).getTagName() + ": " + child.getTagName() + " is not " + tagName ); } return child; } /** * Returns the text content of an element as a string. * * @param el parent node * @return text content * @throws XmlRpcFormatException if content is not just text */ public static String getTextContent( Element el ) throws XmlRpcFormatException { StringBuffer sbuf = new StringBuffer(); for ( Node node = el.getFirstChild(); node != null; node = node.getNextSibling() ) { if ( node instanceof Text ) { sbuf.append( ((Text) node).getData() ); } else if ( node instanceof Element ) { throw new XmlRpcFormatException( "Unexpected node " + node + " in " + el.getTagName() + " content" ); } } return sbuf.toString(); } /** * Returns the content of a DOM element representing a value * element of an XML-RPC document. * Note that some content which would be legal in XML-RPC, but is not * legal in SAMP, may result in an exception. * * @param valueEl value element * @return SAMP-friendly object (string, list or map) */ public static Object parseSampValue( Element valueEl ) throws XmlRpcFormatException { if ( getChildren( valueEl ).length == 0 ) { return getTextContent( valueEl ); } Element el = getChild( valueEl ); String name = el.getTagName(); if ( "array".equals( name ) ) { Element[] valueEls = getChildren( getChild( el, "data" ) ); int nel = valueEls.length; List list = new ArrayList( nel ); for ( int i = 0; i < nel; i++ ) { list.add( parseSampValue( valueEls[ i ] ) ); } return list; } else if ( "struct".equals( name ) ) { Element[] memberEls = getChildren( el ); Map map = new HashMap(); for ( int i = 0; i < memberEls.length; i++ ) { Element member = memberEls[ i ]; if ( ! "member".equals( member.getTagName() ) ) { throw new XmlRpcFormatException( "Non- child of : " + member.getTagName() ); } Element[] memberChildren = getChildren( member ); String key = null; Object value = null; for ( int j = 0; j < memberChildren.length; j++ ) { Element memberChild = memberChildren[ j ]; String memberName = memberChild.getTagName(); if ( "name".equals( memberName ) ) { key = getTextContent( memberChild ); } else if ( "value".equals( memberName ) ) { value = parseSampValue( memberChild ); } } if ( key == null ) { throw new XmlRpcFormatException( " missing" + " in struct member" ); } if ( value == null ) { throw new XmlRpcFormatException( " missing" + " in struct member" ); } if ( map.containsKey( key ) ) { logger_.warning( "Re-used key " + key + " in map" ); } map.put( key, value ); } return map; } else if ( "string".equals( name ) ) { return getTextContent( el ); } else if ( "i4".equals( name ) || "int".equals( name ) ) { String text = getTextContent( el ); try { return Integer.valueOf( text ); } catch ( NumberFormatException e ) { throw new XmlRpcFormatException( "Bad int " + text ); } } else if ( "boolean".equals( name ) ) { String text = getTextContent( el ); if ( "0".equals( text ) ) { return Boolean.FALSE; } else if ( "1".equals( text ) ) { return Boolean.TRUE; } else { throw new XmlRpcFormatException( "Bad boolean " + text ); } } else if ( "double".equals( name ) ) { String text = getTextContent( el ); try { return Double.valueOf( text ); } catch ( NumberFormatException e ) { throw new XmlRpcFormatException( "Bad double " + text ); } } else if ( "dateTime.iso8601".equals( name ) || "base64".equals( name ) ) { throw new XmlRpcFormatException( name + " not used in SAMP" ); } else { throw new XmlRpcFormatException( "Unknown XML-RPC element " + "<" + name + ">" ); } } /** * Returns a new DocumentBuilder with default characteristics. * * @return new document builder instance */ static DocumentBuilder createDocumentBuilder() throws ParserConfigurationException { if ( dbFact_ == null ) { dbFact_ = DocumentBuilderFactory.newInstance(); } return dbFact_.newDocumentBuilder(); } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/XmlWriter.java000066400000000000000000000115761356450004300271340ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc.internal; import java.io.IOException; import java.io.BufferedWriter; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Iterator; import java.util.List; import java.util.Map; /** * Utility class for writing XML. * * @author Mark Taylor * @since 26 Aug 2008 */ class XmlWriter { private final Writer out_; private final int indent_; private int iLevel_; private static final String ENCODING = "UTF-8"; /** * Constructor. * * @param out destination stream * @param indent number of spaces to indent each element level */ public XmlWriter( OutputStream out, int indent ) throws IOException { out_ = new BufferedWriter( new OutputStreamWriter( out, ENCODING ) ); indent_ = indent; literal( "" ); newline(); } /** * Start an element. * * @param element tag name */ public void start( String element ) throws IOException { pad( iLevel_++ ); out_.write( '<' ); out_.write( element ); out_.write( '>' ); newline(); } /** * End an element. * * @param element tag name */ public void end( String element ) throws IOException { pad( --iLevel_ ); out_.write( "' ); newline(); } /** * Write an element and its text content. * * @param element tag name * @param content element text content */ public void inline( String element, String content ) throws IOException { pad( iLevel_ ); out_.write( '<' ); out_.write( element ); out_.write( '>' ); text( content ); out_.write( "' ); newline(); } /** * Writes text. Any escaping required for XML output will be * taken care of. * * @param txt text to output */ public void text( String txt ) throws IOException { int leng = txt.length(); for ( int i = 0; i < leng; i++ ) { char c = txt.charAt( i ); switch ( c ) { case '&': out_.write( "&" ); break; case '<': out_.write( "<" ); break; case '>': out_.write( ">" ); break; default: out_.write( c ); } } } /** * Writes text with no escaping of XML special characters etc. * * @param txt raw text to output */ public void literal( String txt ) throws IOException { out_.write( txt ); } /** * Writes a new line character. */ public void newline() throws IOException { out_.write( '\n' ); } /** * Writes a SAMP-friendly object in XML-RPC form. * * @param value object to serialize; must be a string, list or map */ public void sampValue( Object value ) throws IOException { if ( value instanceof String ) { inline( "value", (String) value ); } else if ( value instanceof List ) { start( "value" ); start( "array" ); start( "data" ); for ( Iterator it = ((List) value).iterator(); it.hasNext(); ) { sampValue( it.next() ); } end( "data" ); end( "array" ); end( "value" ); } else if ( value instanceof Map ) { start( "value" ); start( "struct" ); for ( Iterator it = ((Map) value).entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); start( "member" ); inline( "name", entry.getKey().toString() ); sampValue( entry.getValue() ); end( "member" ); } end( "struct" ); end( "value" ); } else if ( value == null ) { throw new XmlRpcFormatException( "Null value not permitted" ); } else { throw new XmlRpcFormatException( "Unsupported object type " + value.getClass().getName() ); } } /** * Closes the stream. */ public void close() throws IOException { out_.close(); } /** * Outputs start-of-line padding for a given level of indentation. * * @param level level of XML element ancestry */ private void pad( int level ) throws IOException { int npad = level * indent_; for ( int i = 0; i < npad; i++ ) { out_.write( ' ' ); } } } jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/internal/package.html000066400000000000000000000001421356450004300266000ustar00rootroot00000000000000 Implementation of pluggable XML-RPC layer which requires no external dependencies. jsamp-jsamp-1.3.7/src/java/org/astrogrid/samp/xmlrpc/package.html000066400000000000000000000001401356450004300247620ustar00rootroot00000000000000 Classes relating to Standard Profile and XML-RPC pluggable implementation layer. jsamp-jsamp-1.3.7/src/resources/000077500000000000000000000000001356450004300165455ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/resources/org/000077500000000000000000000000001356450004300173345ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/resources/org/astrogrid/000077500000000000000000000000001356450004300213325ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/000077500000000000000000000000001356450004300222725ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/000077500000000000000000000000001356450004300235375ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/bridge.png000066400000000000000000000033031356450004300255000ustar00rootroot00000000000000‰PNG  IHDR szzôbKGDÿÿÿ ½§“tIMEÙ&*†EœgeIDATXí–klTÇÇsïÜ{íÝõ›01á‘x› †¾R‰Š>”(‰Ò(jšF _P¥¬Ô$ôA«¦%ŠBCÓ¨ ˆV©è¶RDI¿´ R¨Ô¸-”F2„ 8¦¬«lóðcví]ïãÞ;ÓÞ]Çk î‘VÝ9çþó?猠‚¹ÃÃud³Ÿ×¾@ëJ´Ö(_ÆsÙÀP<áŒg“ÕUN\ù*a˜f\"® «oÕW63“ÿÉdü‹—.­î»xqGÞ-8Zi|åãzZƒ‚ôè(©DKJLS ÕžY¼hÙf`NÄ6ü§««×÷ü» C „@ RZ»é±”J&ÇS ­QJáùƶû¿ñÈËü¿,¢ù¯ßÛò‰ÄÕ·«Gž¯wâÜe¤ûß1r/~§Fÿ÷€ÈnzH¸¶%r¡ “yâÁîw-Ùz3þYï'ûÌÚž}ŸíX÷«FÃË-¨¨¦óK~qðŸNþKOaø¡j! ®v2™| ‘HÈÓjùÃ2kž+Y!p#ð²@oþrÍ1{Û¢_3à¯äðÎ(¸ Ù½d;׿r\è í‚ ífùì¿ñWÝ2{ ØãEÂþœ5 £1øðsàÐæê7wï¬}å½í…-¼‘|TüØÚã?Ýð†k®¨+=FîÄ~ÓBJ 4àsúëÏvx hžñ"á£7d@Fcëß!àq/>üîo=ýÉÜßÄíbcÎcÜ)úLF)§kb;' gšÏõ–Whs¥ýUà àO2;üÄ‹„/L2 Èhlpجúd4v°O/ym“y€N盄úûõŽe²«G¤K‡íÐ$˜)j½òÑsÀq ¸¸œ’ÑØ6UOá»@[1ð.à»@7zÝþÑ«¯X¿ÀÂãø;¦¸pžúç÷™É‘A=ãýþrÇã¯ß^~ìVÙ=)£±§d4f•lõ"ᯭ@°xøÂ“柔}]ˆRuú<;X±ŠÖz‘p§ ßìvQà°¸ø—ŒÆ6@¯ŒÆöþ´y‘ðë^$¬Å£ƒ‚B †Šº j] @iáEÂÇŠ nžÞ*&ú Ð"sÅÀa/îŸæ¨`ôÒDö¥ß`’ PkÚØÂ  ö´sk¦#ò"á3À“2k)VÚàM ÜçEÂ'*dr;@!ƒ¯ÁTL°Pð”6X!- £ëè-8·=@~d ——d4ö3àsr–à+|OYbÀ¶pËå@Fg<»8Tɱ +àXÅV¬÷Ø5) JUöDL”¢6+¸øÔüÆ14–:åò{}û¹{}KiÒc”´h˜Â€_ÁÏÝóP¦9Ù'Ô{¦èÏëêšåÊ{ôþIñÏÂÀóà•WºM÷Üû†ÓZž;…숩ø~¨«nÀÀóÇ%S>–|4 ÍXn2 ]S‘¥8b>V”©pQ~±0^@OÓÀLVUñ­^AYD^_id€;ÙgÑÅN:xªÖ6 ¸h†Ñ4€æ) ”³÷¼7ùŽ˜E$LsYUK›¬Ú¥e­«­«_U[ßPU×ÐHM}Ã…ÙK ßf)¸²î$+Tù;–­Ø»bùÊÚº†Fê ÖÔ"„˜óLð0Ô dÝÉMVåÄ=‹{ùµëoY„å—Ž›Ç›*ÂÌZÁÊjRI}Ó"¬jm“Àº¡Ì…æ…ÁB¹ ¦j ë]Ç@ÅR«I&­™¾gÒćI Ù²ªµÍÚBˆµõ ok®=™²ü‡‚=¼ÉI¨Oa±˜†Ä&yíãÌÀ€ÔZ36šâêÈ0×âÃ\‹ÇÆ3éNàÀAYߨ¯oZP¿ yÍÍØö„Ÿ½ñO›o§Ò´:àyÍŠ£Èøj<í¹’Nì6ò© "FC5¹Xuµ:kÙ|è8~ÿ·>,äóG€#À‘Ü@oßuÏòÇ~ðSÍMZ!—Õ«›FôÒªa±Ð¼¬?ø}*&k›gm§0”Ïõøž× tGs½‰[mD3Z.;ÎÕ‘±ÿì°H&âc©dú¸V⸸îû¹Þñ›ñ'§ŠSÉué>õL&â—2é±£ÀÑ"¥gr½ó°ÿLÃÓ¥ŠIEND®B`‚jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/connected-24.gif000066400000000000000000000006231356450004300264140ustar00rootroot00000000000000GIF89a¥!"""- 67 IJ999"M'k"*o$+p$+p%G]A,p%H^AUUU1t)2t*2t+5w-6w.gggkkkf~aj€djdnƒgŒŒŒªªª°°°ÃÃÃÌÌÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ?,°ÀŸpH,-ÇÈ4"•˦ô©´H‰‹–ʱ®BK(ô)C½éC‚º½i#t°¸ÏÊõêÔï]_M ~vo‚F!† xM’”Š—™›‡ZLŠ£š‘¦uLšJ­¥“ŒGmP¸¯—InÀ»SÆÇ¤¼«Ìg^W\ÒÖÌ[Ÿ`\^Ù `?Ý éEx îï òEA;jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/connected-32.png000066400000000000000000000034271356450004300264370ustar00rootroot00000000000000‰PNG  IHDR szzôgAMAÖØÔOX2tEXtSoftwareAdobe ImageReadyqÉe<©IDATxÚbøÿÿ?=qzzº2 €A1€±œQ H™±«±< C¥ßñC ¾ Ä—€øôÿÎÿ¯õ[YYEéééEÆÄÄœ=yòä™âââ- q€"èÆ2FU åĮ֦:& ²’ÒâÂì‚ü¬<Üœ, 5_¾~ÿóöýÇßO_¾ýùàñóGO_9Þ Ä;þwý¿ ´< hùL å–_ŠŸb°#§KT‡yÙÚéj*ñHˆ ±Z/^¿ûyùú½/ë¶>¤vSí­£”c Ðr å Ð;¢¤¤ä @auc £Jv²5*´0Ñú˜ pzñý?'%Y,ƒÛ·o?œ9sæe Ý¾Ä‚ayØòÜ/ël[K=!VVÆÿþ‘lùͯpXδ\ÞÒÒ² Ä 0‚ƒ=ÙÏË*ÛÞFœÀþþûK²å÷¶¼aøµG‡å @ËÓ;¶ $@¨!ð!ÂÎF¯ÐÂTKèÏ_â|}ë[–[B ?Ÿ ‹â¶\Í[mα-ÇfÁÄîÆ†K—.- ÆrÓr]†¿šß||,@v€ìÛ Äõ½©²‰€;!|ºó¬¥°°ì%„ÈrO/s°~ »@v‚äæ= I!|¿›ÎÁ𺖬|õêÕ JJJD;â°¬A6 dÈN@Á .(Ê Ž{løÓLnM7p|———3ô÷÷3Ü»wìˆàà`‚ŽvÈæìÙ ’ ¨ä¹yÙYÿüýƒ=Ÿ33T,ª`xôè°ü‹uvv‚CÀÁÁÌ_»v-˜VUU…Çÿ»wïÄt @2dÈN €`fega:x³ï;?ƒ(ƒ±±1ôiÓÀb„ ¨2*Ñbøýÿ²Ù »@v‚Øq°:Íÿ¿¨õÂÛý?„Î(Ã}Ä8XóS¿a‰&Ã/õ¯ 6º(ø *Ú¡V,Þ~ùüí/+JyöXl9È2Pv377'Ú ðh¹¥0ТöÛç@v‚ØsÀÃo¿š±q±¢8€å%Cqc1J^'ä999###†—" q­@»~ƒì±怛o_|ú)$ÅÃ…®¹x%ÆÇgà3çbJb0²TaÀ–°Avì±æ€K¯Ÿ|ü¡h Nt‡Í ê3¤Á|} %\E:È. 6@ÁpúþùgTͤÜx…9ÁoGþ3p„ƒ««+°eÃpÿþ}9/Qp¡£g®ˆ5èAàóÛï?AvÚ+ >@ ¢ÿ« È »ŸÞxó¤ñóá¿ rWu1ªTôr@©6Ý[É á®Ì e"‡·(Ù² l'rm¸ãêÞGŽ‚ï„}µŸ™amÉ P!Êç:ª ßU?Cò·1ÈÜ!öùõ÷Ÿ@;«01€‚;àÿúÿ·ÕÕßâ²æSà1ã`ÐZþMõƒª!$ÞñUãÿüûÿèâ«@®þ¿ñ?Ü7Ä‚ÔnOsÔCi½¢X®] ÂðM务’ LÃET³íá™×ïœ|Õd®@ ¤NÃL\–kå«0|UþÄ  ‹šKˆi¶ýúüé¹wïîx>Èû j3 €À€öX@ytËÕ³•þUùÈ(§#Jrõ뛟?_^þðáé™·ý`Ë·þƒ® €˜_¼xá´œh93Ê‚ð¤Úœ[_î<”âcá`bbbúßÞ-¾ôáãõõOö|~ú½XîÏü¿ X#aZ Ûˆ–ëBÛí ¦ó,FD×LL—ß„[’ƒƒK„Øcàd‡àŸïÿüüøû÷·7¿~~}þãÇ«Ë]³ÿñ¶VÞ39h¹ Ðrc åËaív¸Bw;§;Q;§¸@¡tÍA]ç- t10¿žçÓIEND®B`‚jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/danger.jpeg000066400000000000000000000156571356450004300256640ustar00rootroot00000000000000ÿØÿàJFIF``ÿáExifMM*ÿþCreated with The GIMPÿÛC   (1#%(:3=<9387@H\N@DWE78PmQW_bghg>Mqypdx\egcÿÛC//cB8BccccccccccccccccccccccccccccccccccccccccccccccccccÿÀô0"ÿÄÿÄ5!1AQa"2q#BR‘¡±bÁÑ3SrðÿÄÿÄ&!1A"Q2aRq‘ÿÚ ?ðRÄ©bÒÄ!*æ`Ä‚–²ÀÈ–Ë‘`K pF$T¾%ÊÄãh­¥É.qÅ Ì1(@AfÓ(ø†£&\A°èPcx]&YÖmq¶b¦›W°™YÐâ.zì¹ ‡SÃÀ „ ßEKÃZ‰Ä/lçˆ<åŽ@Ì.œGV¡OÕ*ǰvǬj*ز¸€@̦°“'T$˜™J>"˜EŒ³â±  H"æ0´¼Ñm'Ðä#TÐ /K3+ñ, | Ƥ"X•M‹0À”±Š ŽŠ-D)`bWx#’¢åÈmZÆûŸt•°¥PpYsù˜žÖs’Hœ Ÿ‘øŽ‰Õs1W{¡ç#Á›´Ö-£#b9$Ðìy#7A…L)RåAÉ*I'Lâ1,Çrg4dÓÑÖÐê˜0”uº¾–Ï3§8"w4¯”2Ð]£ÓÅ'(ìǯѕ'iËe# ÏPÝ6¯KNºŸnÓŽ!áÈßÕ‹ò1&¹#’ !„´òP©Ù¢¦¬dcÌÎÊTàÂLVHÓ±úgÈ*I í9Ñ«s‚ bGˆ.63uLØLh_#iuôõxí ½b;`¯OÌ[L¶1­7ؽN“]CnâdâvÑADívž»ÿ—¤ÿPætrÖ™™|._háÇ e„]µš¬d'8<ÆRpc_GR¦uôzJî8±Â‰^©éõé ­Èí2%ý2¯Ô–]Ì™F|®ôzáÄFpfšXû€žÓ^LrÙ(ˆ„Ò;özµ¥Z•@1´ãêʰ8™Zó˜AýÅÇx¸aPÚ yÔôe<ʆà C%qò<ÊHXU9­Ã dŒ‰ÖÓkVò—¥þ83-IS‘´ AK±ø<‰ázèîÛuu »ñæd>¤‡ŠÏç3œÌ\å‰'æ ‰.ÇäóòIýtšsYôÖ ó2IÉdÍTé2Zp;梉›ÉžVö•2ňع¡¡…UQâE¹[+Œ8F¥ÊÄä *I$&„âVw”NһΣ9V+¼éè®<Ì]ÖjÒ·IÞ'"´[‚M3®Ö•^ fmzû”­‚N®¤ÄmëŸNÎ$ËêÑ|—Õžn$İ' xI¶¯Ü\wó,0<Á±Š6©˜ž¶O¸~°'H.G†Ñ£ÒÃ0”×±3ñ¤·™$†Õ:g*vï3Mv:½Eµ +N:e+ÆùU¦qJàdq Á8Äê[¢GO¤t‘ÚŸÒ¨äØ©·8›òÆ­/"t‘Î*:3É—U/st¢äίîZzXgø„r8›+Zª åü¼)w!Òº]ë=OýXãñ³Õ“¼aÈÞ.÷l§ŠŠ¨Š‚˼i\  þf¦‹@ı…QíÌ4ÁqýDzOqýâÈÞ%E(³.A6ÁQ±µ/ðó¸Ý:u&>!S§,pKg¡:L}Y+3n·øz žFeé4¸ ‘êZÒ†Òkå5EÏù£Ï€%ªâAäCöx‰ˆXmAX…;ã˜xóˆh£ üľ‘Óø›´Úfµsª9$Íbš× §#Qóòqz,pœjJÏ;g§Ü(¥€îÏìX?”Ïséô*!r@_'©^hjºW€›%·TJÿÇÆRú½wK¡Êû·© ü«ÇT-HÊœ0f¦Öq¹%›éMAªm†`æ3›í„ühÆ<"r¬À%{v1`âtµzpšUÛyüÿûýNv7Œ“Z<ì¸Ü%L¾¢;Ê,a Éñ/Ûù„!tÀÉ”6†@óL:Þ‹Rµ78ÝÔíÌÛ¨«;ŽDÅû?¨ª«ž» ˜éñŸµe|í Ë'‡³â4ð¤sNú„²#š¼.1ÁŠ#¸›vRŸèæaœd•ƒŒÁ#„`dq£‰¨ÛËéÎâ.7†@…g(¡dc;Ä5jNNf¦ÌÍcµ_zõ"oМÉ%rZÖ yˆ²¾ÀÚ9m[88> ,dF&×d’„gü˜ÅŒ9š´Ö+8óÚº†Gi™S ”‹–)ohììÞ?¤™ÙÒR1ÕŽgÒ¿»Ò3¸ž¿ÓjÅ!y‚=œ3Rƒ’è,¬±öœmQͬÆwµt‘Ø çu‡ê186Ç).-œpvÞóx‡=3Ȉc˜\Á ˆh7š(¥®°" “*;0å§wÓ=ª›¦¡’£êo1']Æ.(]ú¦­+’GÔG˜ê´µŠ; Ì»˜‹Q{uJÎv£Vzz'®G5*«5ëu¨*öª(6üÎ;¶w•îb[xj¹Älb¢2:!b²à±çñ5€©¶Û¤ÆSNzr8›+Óõcc·æòIìÓ ©éaF ó ³âz¯Põ]6…ª·Q‚»}©ÿ“<¡É2Ÿ•6Ï'ÍÉIqôL™$–%DE3´³(N8ŠJœŽDïÚj>˜õ‚ÊÜþ“…‰\ñÆÐÌygùg¢¯_¦ÔýÒÇù[c/£êÛ‰ç8ÜM]©ô{§çûÅ<ü•ÃÍÿš:¶ÝMg bƒã1'UGÿ œÌ®!¬(ççÏÒGgÜS½nàÈmþÓ8Ëc# N–›P·/IÀaþ`Ë¢Œ>RÊééšpAúÆ!š²2¿Ú_J7PÈÄu¿KD»ìµÙH89€Ê0s‰«Uv™jêg†éØï30r!FûÊ2´Œ–i•ÆÛ7™œµ´mó: ÄÏ}Ž•5N:ªs|øJôÏ/ÈÅñýàUv-ƒé縊¾¼`‘…<ë`Gé6•:Šƒ Î;Cªv!åçkfJœÖùS‚'Û ŽKóv¾å­™+ûÜÓšyÚSŽ4Œœ†/3e ` É·RPôׂ|ø™^ǹÉü˜ß‰È’~d`é+==ÚÍ6šž¶±[þ*rLçk=yïÓš©¯Ú-±n¬œxœxÚiêMÀÿ3cãÂ;{$ËåÎzZBñ¶L®x–ów”Ò|Iƒâ^ß2§QP0¥N0’I‰'I$’q¤Ì®¬ñ.LN0 KF(Á‡"3'LãVžŽ¼Y¥,¼í‘4+eCóжayÆBÔOýÜÕ§9¡$óŠHõülï$·øf×i¬¬ ±šì'æ–ïr ¤ýK´Ó¨f³BúqÙ½ÁùÜäk © ˆq\¢K9<›]3©ªVeÆNu-î"°ï)6á@ÜÅ©q{-ž/›Ž8Á¤£lÊr#ý/R(Õ/¹m¶lÇz†€Ô G"s#ÓS‰ä8Ë Õž§[èÁôíª¥²¼ôqäyžzÅè'§¨ôUSéƒOoÔõ¤y‰êt­z—(0GlŸ' ú(Í⧦‡^Ô©¨Ÿ¡ e¯’g0© ñ5Öýu‚yGJ ;FáÍ.<Þ̬F°Ú數ÐH0Âuô ¬\ð¼Î]cø€Ó·éåk¡˜òÑ9^бª‰ÓÝEqÏiŸÔuMZtŽXc3&žïwVzŽâ+]qÔ]ô†ÀIÔ7°”~Û0[¹$ÌÜ!¼w¨ÚQ…cœo9ÄääÏC5ló<Ï!¹8D™•$‘ÇžHlp&«QzGˆªÿ‡Q~íÄK£’g0'0z¾$ L5¤“3A$ÅäËÌÔšRq±Ms’±6aTȆ*øÌé&‰Fæ3÷zûLä1b9 NÛ³Y‰ÞT=åÿé¡Á ‰Êf£vž½9³ê•#|çÕG¹ª,»Lð²¹·øMå¿“ì„hoj/VàγÕûÍ.9#y Á¸ßM±tfc‚62Ì”¾Â±NMp}¦¨äŒq*°TŸ ª‹‘±35„ØLR½ øëcÛˆdÃ'˜*{ÀG ûRý@ζž¢iùœÍ9àÎö0‹ž:dÙ]*PчJ¾Û³ÒS ÷ƒ6[P 7&f`}ÆQó;5þž_PímÎç¹€™ ÔAÁÂc§¥uÑótÛ¶gè”FàFØÁväÅ/$øšŒ•#E¸ePÀ@ç´´Ö噑¯c«§1éIÌBj6Ú0_˜øñFÅE³~‘žâ¨ÂàLBéFñˆÎf£lY·æf6íÌs|ó7‰ŽF“mÃ-nÔçõ‹]`^P£÷êzT‰ŒÔ×èÚn~¡“þgAX8$¡Œ8ëºÐ0"êõ*|‚H<‰œY’šª¯¢ÊîeÁ$ ÃIž¼ õo±VJØß>'W¥(H#q2'hæCE$Ã’Û ¯O¦-À)¤3'6ZÓí髳$,»~“žã Üõ+¥ èÖ+.^—ëƒÏúžoR˜sŽ"ñääHqz:~…A¾‡=ƒb7×t½ÖØú€ÄÙ]J¦¬éámÜ™Øý¨®´Ñ" 9êäœçi4¦ãž¿J–E< ?‡Œí=oì—­®›IfŠÂ²·Çy喢͈ê«jmV¼~hG,x²|Qݵ£×kýD;ºl;“æykëë\÷ÌÝ}ýxO剛L|Eâ‚ÇC²T宄.™zrÄKÓÖIJ'Ç`»A’c-Æ–¾‘÷aò}Â"u,µZ™Œó’I'“*2*€“³N70’±e Óìg¢©ºêRœtŒ™çj8oJ½ÖÀ­öžb2«ÙD“£Ð €Ã~fKñ ÆùšžàmÇeSî1^çpdÉÑÖÒ¶quºL†tÀç ÂÀg;gzoRc¦ÓÚ¯÷xžgRvEøÌôð·(¦ÏÉJ9BIɆF)Ï–‚¼Â±DQÛ$Ç“&$–xœjE†Ä/pÅÉ:ޱ¾áó'YŠÁŒD&`I¶$Æ*æE¬ˆÎ ¢Ä¿Aö‰àKýÙÏò˜ÅÖ øLþdQµ¾Å 3a}DÙ¥eø™™8š¶?ÜdOiܨÇ.ާìö°­O¤°ý<¯ÄÕ}&Ö;nóB5v«®v3×ѦR„÷Ϙ¹M-œñ4Õž|h=Ë@Ç‘:ú]SX/45bŒ·N1æb·UõeŽdS”çÑéc‚ªF ÕÿµëB³Éjjþ+æzZ5›—#Œÿ©ÇÔ¢µÌ@Îñ˜n6\K’9®‹–Ä$2œ‚'sU{ݤ«ÜbÅŽw˜l 6øÄ:ؚŸåâ=µ*‚ã…Çôeu úŽÀÄjzÇHÚh ”D=-ĽÛ5ô¤bМ„XUWõ |ÅÚ6ÇaÞ۠⬺¬*Kƒ}ÆÆÆsæ ¾v@†£ì/D—À”72ψF#AÚE;ï!Þ@1[ì583v’ÿmº±“âaQÞ:¥,r;sI4Q‹ðïiºu¡¬¥‡Ü<ÍUã#qÁ™}({UíÔ172ôSa9RA˺#§^#ëZ–»TÀ³ÐN;±w'ÌÙªË\Ùí1“=˜.1HùùË”›/2›™$<Â’àÂq¨˜–\ek™–+ÉšR°£'‰J1,¶7<ö‚:*‚`å¶ð"€,Û S㫯·™tod]0l LªN7š»áF™¦;±ÌT¦‘F<.G=4ù;,ÙO§Ygl™Ðª¥@zT~L×E@\“áVM<ÏÑjÅ-ˆÑú?S€N~ô”Õ]4ª•ûFy™Eµé”UV#}óf¤¶áòd/,å+ô"qy?èÕ®©/¤0Þ'ž¶•­¾³úMïc¨±™/ÃŒ™t«GcoÕ™:•,‰šò=Èz€@39rûŸ8ŽQÐY2[E>〬 ´V40UêÆ [9fÉâS§l É)h«õs…ñæbýàÚHãÀ•©ËœÌààæS*<̹¥Ëý¥ˆ*z€ù…ŒM l°$ï&O‰cx!­šd•™"ˆ&Ñ•›3Ú$M ‚ 1ÐgNN]@ Övc¤°“•+ý§OÎAjµJ”5v²Ž @ó´’jžŒÏ£Êë“£Qtæ°Á=s±È=„ç°ž²z>r¶.@!,Û6 ,1(™P3E+71ÈN0ࢶ5˜ – £9â2¥$ñ5×ôŒ(Þ.S¢ˆãä)4äfš©$ï4ih¶æÂ žóRÔQúwˆ–OEPÂ4ÕÔ¸QˆVZtï7ÒšÀ õ¼Ái79ÄG.L¢ƒ©=Ð:McŸ™­,ÂZô¯ï2Ó†uVØG³çéQˆœŸ‡vÂ8#x ï‰nvÚPGæ)Ãé$LÚšJŒ¦ãÄÖé‹s˜Xå(½âŸgöÉ8™ÂñÞuu#ã äNmÕ•>GÄôqäL—.9%h4"Êñ‘Ö8ù‹`À`ƒ˜½×q ˜²†'=ŒÇc1åµL[4KœÇ2äD²ÂFM¶gq30èæ{— ˜øåV¬e¯â0˜º>Ãù‡Þsì8(1Ç2 RÇF¡àKIZ€ºÆLÀ™ÛQk‚¥°a$‘HƒÉœ”šLvS•bðc¦æe-k¶8ÉâI!´ˆÔšÕÕmu˜ì&ÆI& We’dÉ’I¦„£1¡I‚-PZ $˜ÆÁ+5V¢utzjØ)#;É$—+Ñn>Ž¢ŸÝö¬Ä´P¬ÀêÏ&I$g.ÎnªûÂŽ Õô©#™$”.†¾‚Ó}V3x›P$’l½ƒúD ’E ¢šI#"aší˜.ƒ$’˜eMþžÐWîéìd’Vú!ôAÄ[É$ÄPú0·þþ [¾d’=K¦?gë½ä’c"χi$‚5vÿÙjsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/disconnected-24.gif000066400000000000000000000004551356450004300271170ustar00rootroot00000000000000GIF89a„"""2W$W%W&999Y/$Y0&™3`MH`NIUUUggg™fkkkŒŒŒªªªÃÃÃÌÌÌÿÿÿÿÌÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ,ªà'Žd)6h#Aéž–%ÍlûŽ jÕ|s‹ ‹¡Á«5¿Á!Ǥ!2i{H)â4fµîµÁ$GÓðõu9¤´¸mrT*pôÖ@ÿ >v %S%1‚„oq% Œƒ€wy7d—Œ›?W  BŽš?˜,¦˜Bªž;5®¡I*EµB·+¹ }•½=GIÂFGgÉ( À/,Êg$!;jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/disconnected-32.png000066400000000000000000000033661356450004300271410ustar00rootroot00000000000000‰PNG  IHDR szzôgAMAÖØÔOX2tEXtSoftwareAdobe ImageReadyqÉe<ˆIDATxÚbøÿÿ?½°¥¥¥rzzºÉáÇEabÄ"ˆ;ut¤€”›±«±8Tú%ßâK@| ˆ¹_¹ò Y¿•••³žžžmLLÌ“'O‚Ô>,..~ @´XH…q ¤–²¿”(¸— ;7'XÍϯß¾½ÿÄðåå;†Ï^3<¿väõ@¼è«@Ë〖ç-¿´ü&PüÈÁ·oß~@8´˜H¥qªŠ‘‘˜º¯˜Q¡õùÕ;†W70Ü9tîÜFNÎëÿŒ£–¿ZþR7–ߘ9sæ€Âê å@ªDÖP£XÆ@OB˜(‹7^½Çpõ ã/?PÄ–3-1_ƒ‚hù å7ib1@±à°¼AÙÖ0]ÁL›™•˜Xþ¶üÚ}†ïþ2̘1Ì?rä\j9ˆ-gZJsŽ;v €PBìÝÊÖúÅÊVz„-½þˆ2°KÊù3fÌ`èííÅ©Éòx å‹@bÄ„¦&]F_µXÞXƒáÿ¿ñÆkæoÜÆpéÒ%°fb,Ï41Y ³ˆ -µ§Jk+103âï_‚øæ›`½………`G€,!ü¿ׄÚ„¡JæZF¼Â¼ ÿÿþ! ƒÀêÕ«”””ˆvÈh¶€bB*dÅ$ˆò9 —šk0ô÷÷3Ü»wìˆàà`‚Žøöæˆ „ÚÉ@°\`'®"­Ç-À ÷1ÀWIœz€Ž…€ƒƒX|íÚµ`ZUUÿïÞ½c0’•óAv½¼óTª® ˜ÌøDøH²îEQ FœŽ€PTMq³³Av*ÒWÌzÜü\ ÿÿvÀ­—Ž½Ú )ßÅœÔøu|äD€nøÇЋÅÀ"œúA–Ûp01hÚ3¼þŽZŸ0Ì*œÜlÀxÅï€[Ÿ®2{³Ÿ!mæ0Vº ƒ&$$ä€Åô?쎰åVú`;ÀvA*3€‚9@œ•• # z^ügxü ÂvexÇðö?ÌrˆàY`èË#ôøÈ €£æ999###…ïoÁò0óAvÁjR€‚Å àÿÿ‘+{_31¨Ù¹0T‡†ÂªS†cÇ–ÏRÎ6 Îó-lÔ¸5¼¥øÁÑÑtÄñãÇ"õUÊå%ÔÕáQ ’‡€‚9àå¯o?ä9¸Øà³3Ì«¨`xôè°ÞZ~ ê¨åŽV '>AHHHñ28ðÈ2|Óaøô®"Jôþúö Ö†` ˜—ï|ÿø @¼ù8‘03L›6 ^ÄBaÉ ¨ëÇpâÓ1¤è0DT?s=#*ÎÏ ¬)‹" » Õ2@ÁBàÒ—_ù…8¶>ÿ°—Sž‡Aä@ Ôè0·5%:íb€¶ž§>¿ÿ 6à&8Øre `nnŽ#$r–[1¨°È]„ƒì‚6݇^?ÿtIFAPï ³0à  åÈ5ȸB¢@ƒA…™ R<¾~þɲ d'ˆ@à€6 ׿}ù £1ìl!1áÞ'¢}ÂP;Öí„\®~tïý9|®ÇæPV#¶òúòá;ÔŽÕ03^€Z¯Y¦¦×L#|Žbpuue())a¸ÿ>C¸š¸B"þýûÏðòÙgs6È.˜8@± µÛÓô ­Wä6F«@e;¼ÙµŽÁVQ‰¨øúø3Ëç_@Á6Y €X –{+*tËÑëuP• «Õ@e;H ±âm´‚|þìéW†Ç¿€,îú¥@ŒÀ¢Ðò$ åÆ@ËubòÈm8d®"Î`«¤¶\Q”…pŠÿú‡áå«ï /_~ï…Zþ] @±-gZ~Íçò°$¨ '¡,„’.YþíÛ`hýdxüä+(ÁÍ;ºÏa €ÀÍr`Šùä{ åÆ@Ëu!ÓÌf³»f"Âz<<, \\, ̰Zá÷ï ?~ü[üåˆ7o tÍð9 €àý#€–ë-7Z~hùjvNq€Bé>ƒâ”&èÙe0³æaÐYâ¹IEND®B`‚jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/ears.png000066400000000000000000000037021356450004300252010ustar00rootroot00000000000000‰PNG  IHDR½¤Ü bKGDÿÿÿ ½§“ pHYs  šœtIMEØ /ŸhãÂOIDATHÇ­—]Œ]UÇûœ}>ï÷™;3·ãL§ÓN‡’ú!ƈQDy4ƒåÄè“DcôÁÄ£<y‰áÓ%*$U•òÕ[h§Lé|ÜNË̽{çÞ¹s?Ϲçœíƒ2ÛBiÜÉNöï_ÖÚY{­…RŠºW-fTÔ¨©v¹¨~ûðýꮟߩ”Rü¿öòÊ¢:²xlæPJqìU6›Å t2ÃÃ,—±,‹Ÿ›eîÄ‘óÂýçç÷+G ì(¤])Ök„õ®Ý÷}KêÄ2Y0­ç1Àé¥"Ý^ˆRŠ0 !ð½6®®Ñ¨”©WV |=ŠèO¥Îóvߞ݄=ŸT’åS•)[bߥçû@slš=ÿx뮚”Äãq|ß§ÛiáØ&Aà#¥†×i³47ËZ­†i<ô‡û?äµe˜DAˆгËïsæTKêX¦DEA¨Xo´ÎB+‰œú2}ŒòzÛ΄'Ø%ãÈ#‡È-ΑKƨ.Ÿ¡1¶}ú³?=¨ÒÍ}6æko:ò“JcÜñÝg½ˆöØ+}ƒ´"üg>/>®4ê¸Ù#[ÇÉä‡ñuõv‡–ç#…F§ÙBy=Ònœ‰á‘ ðhÿz¨P^v³×õAèèÒ¤E¤s9ŠË|jÏnzâ\ ä‡+÷î”WUªÑ$§"Ž?Χ6qÝAF§‡ã8lJf6 ìÝ‚‰DÐí…ADhXÒ¤¥ RÙ,¹D ’ ?ܸ§ý÷[µŸÀµù(…Œ%èD°)Êž;œ‡¾~|Û¦-å‡2AûßÔp’i”e³åêݸCÃhé UÏÇHg@j4ý.…Âü†þÄÉaNØ#r]Ö‚€¦4ˆ’)6ïû4ž¦ñÑâ#ÁÙDLt5;ÛGjt”¡ÉIz–…–L¢´ˆÀÒYª”6ô'O- ‡Ð0ÐÓ :R`çÙq%ä†ð ‹Z£q^îkúþzÖÚ-ˆÇI ™:ØJ׈§S(CßÐÖÚëèé$Òµ1 °m2ƒƒ îØÁz±D²¯ŸÑ­»Ä%˺”Y¶}ΦK¶d%á4šhžÏwoºuÃÐ?¸K”ggéÖjhñºe¦ôDD8f®Y»àß~AðÄîë…m¹D vîº iÚt¼JÓ)®”ÏÓŽ0$D!…ÅS¤2zaÈÑ£G‰Çã—¨¯7 •@7,Ð%!‚@h¸©ôyÚZ½A¨¯KÿРÛòx=Ÿ={÷2´åŠ v©B©‹×ö¹C/¨X,ÆáÃo355E×g}½ƒïûÈ|‚ùùy”‰iš´ %Êå2ŸûÒu„aHY›¼ò“ƒÎξ­ó¬Õj,.ét: ŽEŨŠã8 D&J)]¨V«X±8Ýn—m_¼î¢=¹ü(èìáƒjdd„by…ýû÷#¤Í¡C‡xøÑ'ÀÄt¿¼ëÇ* C¾óÕ[0- ×u?¾+¼ØÈñÖËϩҴúã#¿Qß¾ík—<ÎÜýý;Ô?Ÿþ‹Z›9®^=ðÜEï]0Ô¯¿ð¬šå•W^âGŸ¼ìæÀóO«J¥ÂÍ߸ý<ÿÓêÂq!k8CIEND®B`‚jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/eye.gif000066400000000000000000000021411356450004300250060ustar00rootroot00000000000000GIF89aç7BCHL‹’˜ÕàæÜçí÷ÿÿìõüÂÇͬ­²QPUSNT88:£¨¬ßèíðûÿìûÿôÿÿñþÿôýÿ÷üÿÕØÝ•”šZX]LMQ ¥©ÄÍÒõÿÿßðøñÿÿ×êñòÿÿãêòÒÕÞš› bbd]^b…ŠŽÖßäîûÿãôûðÿÿ–¬·•­·¦¾È³ÇÒš¬¶èöÿøÿÿÀÅˈ‰‹RRR=BFàçíëöü‹£­­ÈÓ>Ze'-9¡·Â¯ÁËðþÿçèì===RW[…Œ’ÄÍÔÝð÷­ÃΞ¹Ä  ™­¸®ÀÊâðûÊÎÑVVVOV\©²·öÿÿœ´¾.JU½Úè¾Üç I_j—©³íûÿêîñÄÄÄBBB\ag—ÇÒØãöý£»Å ¾Ûé¼Úå"í÷ÿòöù’’’SSSBEJ©®´¸ÁÈ©¿ÊAYe»ÑÞOam¢°»Ûçó”—£££DDDQRW€ƒˆ·ÀÇóÿÿ©»Å ¶Á  §µÀÌÙâôþÿ±¸À®¯³¯¯¯ZZZ_^c“”™ÆËÑÀÊÓêøÿ´ÆÐ ø™yv:ØkÔ1ª£.Çœ0úî>tMêñpx0NÀ^`3p¦¨¹Ï ©ÁÜà7Ó1Ú~B´¾Ëãá"p·Áf໕¥Ì}qX4=¼4ð…$çN‹ÖÕ¾Š\ Ì|¡|ï¿-îÆ-‚ˆÈ’M"¥°º9ÝNd&Îu Ûø€?Mûßÿ¶ÇcnQÚ~„“ß(g»•ñä!ÉepmLÝ=,ÏO%ðÕÓÛÑ™ËvY{îÙ-*FEæ[ÔíF«*Ð/?G¹.ü$Z¶’i` À“«" tžrÖü`³h~ð¿Þ¿%Ã!´íkû7‰«Fw=AxÉwVo RSé¬ùñVejÚY÷áQh9lÙî{¨Þ ¨Â¥+LB8>{_î¿×Øôÿ¶M9ø1ç“)Ž °xuk{>=`ì$–¥ŒÃXlž /JŠ`EØÀc×a[Ô¢£‹`Ÿ+³&ÿæ‘Â-¸g“}‰~¿PR,„K…¢BÁ·ÌžLBó{ÇNÒ“¶øèuÝÜLÉ$ÿüÒË‘°z«*noØ©*©|pÄâCŒÏÄiŽ-œ¦ àòLsöuy>õÔT n÷â$ªJ<‡ŽX¼ÕB|,Æ^à¨ÓŽ6@9ðZ0ÀÕèNô·DSƒÙwÂÜÑ_;ŒFw¢?À3K©8Ø´FJ‰5E±Úï3:7d454Ý~B´)ІC$€V`S¶#‰S¸0pð P›952²¦Q øhËö·ÿ*ô…!²IEND®B`‚jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/phone2.gif000066400000000000000000000004351356450004300254230ustar00rootroot00000000000000GIF89a„ EEEYYY222... ```Ów ìì***gggÿÿÿÿÿÿÿÿÿÿÿÿ!ù ,šà'Ždi–@ g[B¸ôP‡@ï’(<š`t1tÅ“ <Í’£ðˆŠ ‚ˆ@R¤ %6©TZª!,°.ŒÙ$8 ¬Ñ#£!ÅÏ# Ir$ HE†# `;°ò½NB. O@K`鉰(CåÜb±v~"u­6[½BZÀÄm um6[’|Š¿Û”ÿ)È M6Eìe`¢ °h²—‹-6ËwµSßeâ5lž™îaDºÉ÷ÿÔN«eâ›Us||!Í@Émì9©nÄ×óŠ ",q+º5-¶`  µƒa||΢!d""t9&{އ¨¦Åê^ÃdóÁ^œ´ç¤¢yí𔸬‚¢‚^ Ýœ2#\‰pfH¢Ö6  #( *Dt„€@v²ÑGËÄe7l¡(Á­Áï²S\,cñÁ§6¹iŠÇ§0”pª¶•ÒsÃR…¥w%b…#Œ/*ð÷ÕòÅçz²€ž8­Ñš¡‰D vXc¨+¶Ñ„Ã`W¢OÂaRÝÉõ=Á ·Ð;ˆmJσ¥ LºsÁZ\Ýx3óO,àú²¢f™ü¦ÝË*×—Ü¡6ìΫ íØk. ·Ó@™nÕKiôMrŽ#Ôw*´˜‘t®Y>ÍCNŠq}yã\oïú=G¶+ÔÕgßëKà¶œ< î™Å—'OcPæ­—÷”’(Å[:5Oín§Õ20!B(¿[úâêìè`ïîíìx}3*|ŽEßÖ̾_‘— ?„,¨>{÷5RòÚG¼±ùÿ>ô..yšÜ¼‘fÿeҲÈ€¸îBzæt9;~·™#ûÿʼ™¬ø¡bp†DjQe<— y¹Â£ 4›¶†Y÷êûüâ¹jýàÆN˜‚R*. ÔµxlY!Ê•R²úyÊî`ÍólxE18ƒ>`®–€_xr±bË:!=XÉÆ•?¦òäñkSq"ZQ~œßlx™ÚêlxIóÐ|…aÄs%Ï S' ë_ÒƒU”¬~¦†úx/7®ÎWŸá­]Ûhª9DñJͼBAäúÀôõ¥;„Ÿ…ºšRÞÙý¶Óe&ÒÝŸ\%]Þ¿£‡ÞfåOfLsásÍá28Y9 ¸0ad’(PwO…oÍ ñ·wßäkÌ%}Pf¿ˆÌX.«ù¬š£ÿ:Èô)|£P¡T4˜5%š-Û5ë"9Æ45ùÃ`ÅÓ¼Y}ï2…ï>,üeï)ÎVžèP<—UUž¢µþ8K¿£ðy£Ý´s¦ø5Mí%.gYÛ†ONÂÒiÞß­iîPaÒøª*O <Ê>;WÅ­µÜ>ªÎìzKÓÜúµð«W£«¹ËwŒ†ê³•²æÆz†fYø¼1¢ïL|2ÿóÃþ >8šêe¶e‘’»ˆ»]ñ55÷W[5n7ض5p—¹<ž¸8q||@nw Ë·€Ëí¸Ë’SÒ¨oÇéÿÁ9×Jsgôß}|zV“’š>p—eeçPuÞ“¸Ç ó ûß•O.Ž>º±ó’›7p—eeçòy}‰ôÓWKZ*üt¹bÉcBz*˜ ógÁú—„ñc$Š?ÿ8 ¹PŸÃÐÜü˜.‹™“SÓÈý›¶ndÊD!Ð}Â_)aÄ0ÍÏ– ß[(„B驜 ¦¾~Y¬>r¹y#„wÏàè©,¶ls‡‰²”RBFº0*_{»0b˜ô &‚ÕêÚÆ0¡`‰IÉw™ašÜ–3œ™ó³ªØÇ¶ýƒŠßBk, Š·8üöÏIÜ?÷a&|uZÜöÃŒ×~øAî¼·Kµxö翦©©“…)|^}ÍŠ¯µ¦½Š·:¬ÝìeæƒË¸ë¾Ù×j ®ÌeÒû6¢÷„™”BáüGðúXµéuŽ«á©%ˆá`ƘñÃa͉ XµÞáÍ÷‚<²d÷Îúf Ý fˆöPäy5(¥™YÌY°!Ù9ü}÷Ì~¬Œû¦6±`®â‹£à–`¤ƒinãåší;5»ßq‘9ôNž[ó ÃòGÇŒ´Žä;F‚OœÖ¦X¼\è%à‰Ý³…m›Æ†KT–ã“?âlE9kÎÓÕÑŽã„QÊ 19…‘c&PpO!£Æ~%ê ¥è¾Pè¾ÒPVméu´ˆâ/*Cy­Ñh¼*Œ–›72Âå1H;¬i³]ˆœP{Ä7þÅe(wà&]QŸÚëQºÛRN¨]plþ `ÔXa‘ËhIEND®B`‚jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/tinycalc.gif000066400000000000000000000002341356450004300260330ustar00rootroot00000000000000GIF89aã  êèìðíòñïóõó÷øõúúøüýûÿùÿÿþÿüÿÿÿ!ù ,IðÉIë À-p:P —}&ÅnÜÒ^L2â,±‰à Be€- ‡îÁ*)ƒar20–,™’Q8’ DÀÀ`–4%;jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/images/tx3.gif000066400000000000000000000002261356450004300247440ustar00rootroot00000000000000GIF89a¡Ówììÿÿÿ!ù ,gœoË>BAFh-';? b·€W8>çª"§šíðªì¹)NÚ. ô™œ¢ïÂrC¢rRÁä”#ØE: –ÌéO›]e·_„"ü4ZÑ™,¯áà7ýg5ÞŸí9:…r÷27HW;jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/jsamp.version000066400000000000000000000000171356450004300250110ustar00rootroot00000000000000${pom.version} jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/samp.version000066400000000000000000000000321356450004300246340ustar00rootroot00000000000000SAMP REC 1.3 (2012-04-11) jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/web/000077500000000000000000000000001356450004300230475ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/web/clientaccesspolicy.xml000066400000000000000000000007711356450004300274560ustar00rootroot00000000000000 jsamp-jsamp-1.3.7/src/resources/org/astrogrid/samp/web/crossdomain.xml000066400000000000000000000011401356450004300261060ustar00rootroot00000000000000 jsamp-jsamp-1.3.7/src/site/000077500000000000000000000000001356450004300154775ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/site/resources/000077500000000000000000000000001356450004300175115ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/site/resources/bridge.pdf000066400000000000000000031103451356450004300214470ustar00rootroot00000000000000%PDF-1.4 5 0 obj << /S /GoTo /D [6 0 R /Fit ] >> endobj 9 0 obj << /Length 748 /Filter /FlateDecode >> stream xÚ¥UQO1 ~¿_‘î$.í$Nx¡"1ƨöÂx´ch”B©û÷s.wíUÐ U×slç«ýå‹ ÊÈyå"k㼺œF£ÊWó+Õ_~:( ÝØÞ)]IŽZê© f8e¨{Ù-shRú¶Dï öÔ,cç § ÞÍŠ“B,‘¬öÑz…u  þÏ?/#êèÑ·%nÔ‘ Y‘ø1ƒßgçFüP@b”B©¨iˆÚr*;{nŠÓbU¯Ð`½&^ÀÊûŒšvf·ãO¨šLÐÁǨjAS€æ¨öFÅà=D…Q³ V¾)‚¨† Y ¼ÏÊÓÝ£UMžË½ùõøjR‹ýÑßIÓd½S}ô·UŒÂ÷ªn÷¾™Ïe¼´K´V}Ž@:Á¬–€Ž(Ñ%ZÍ¡!ŠH‰“ì´‰É)D}­0”ó ÊÂÙrT[6Î_7³îä@‚Žédœ†\³û‹qf÷a1Ÿ€½0¨ÜkV‹Ù$Àsîåöa*Õ“Íbõª{±®–=±ÊY Žmk:DfM$g Ñhg¹)qø95qœ¾vsuÃ[)oQÕPNæ³»ì;šL’cqbWÛMËÑit"²ZÁäŽ÷O3ŒÚtRTS@ŒìC‚š=N¦é}QÕÈeƒ>Ïa4&>çÄFÐä”G·ö¬Ñ;Q¸¯Å@ðÕÀÝ@°í0™ãղϱ@ëè€?hÏy&d¢QTè6Dwy]Zj~k8Þ©j‡®¼h®œ^Lž¶³´ÏFê}0“×&ìÙ1íjz±ÈÆþÓ]6¶ÖˆÇ[m×”P”aÖF_œ‚ÿ ;Ãcè«®G†dt ^¨ö– %§'/Å)ÎÛE+QÑ]›,ºk­ƒå\¦¬ïb\ß^µÁÿW Y–IõºÝì„Òmó;©mÄ+Ë<1žñ5v—y}ŽaðWùìüæ•wºù  ª÷endstream endobj 6 0 obj << /Type /Page /Contents 9 0 R /Resources 8 0 R /MediaBox [0 0 792 612] /Parent 22 0 R /Annots [ 21 0 R ] >> endobj 7 0 obj << /Type /XObject /Subtype /Image /Width 763 /Height 329 /BitsPerComponent 8 /ColorSpace /DeviceRGB /Length 415115 /Filter /FlateDecode >> stream xÚì½tU–øío÷?»;;»³³“w†4؃Á9'ÉÊVŽVÎ9GKrÎIÎÙÆØ`0iÀ€`Àà Ü’º•å˜dl+uÎ]ß}ïUWWWwµTrËáœ{Þ)U—º+¾û«)e;5|Qw"QuÀ²IÑf”·dJÓŬG ð'¬ÔtÚuµ]”®‰¶‹^©²/ðCv…’·˜¤FÈJ†?RÀÎt QÖjëû›Ñn« ß"ûÀì]³+d·mEÛeÒÙƒ¦C¯nשÚ@`ÖPÆ‹u™Òw ¾ýártÌ>ë{(ÃE$Cþ >'ä\¡Ó…ÿ4*Ûôr ˆAÑJŸC|À  —ŠA`þ„•Ì}Ø7£Ýl ŠN‹XÖÃ7·™`”b£¢ Ä PêV$* ˆùÓJÛËzYüIÖÃ2ì•]á½ßî?ß÷sž#æ™Î»‘ FÏ}bè¡ÿÉ܇–Ç,Àý`}÷ZnÇ÷?ŸÀÚÛOÑø!xÛà g‚«†…¾ŽvÄþsÊsÞÐ}Bn®0_h}«ØßX¸h-?¡‡“Ùª“ŠÈ­hWÈaAÞL„³¬A[—kÛ‘ÈÞ˜ËÁ‡&ôùå™ÏéÉÙ€<hþoç\ÛóÃÏ©Ýy‰Lv~—žXŒr ròÙ¿HÖðgí@3=Ëá§~~]7Ø"t>ÿB;lý èDI[ˆ 9 ð äT0Z›,£i‡™—x&.f{ŽŒ`øN…ýó£âE†@Èþ›©£ krPua鱈Îs™èS¿‚À†xhUF“Y—¬ àÁÛÀÎó5ùX“€h‡x°V²»1ŸÖ>àaïù0ÿÑ|6þæÚ#š+¬õµù©á<>v× ä´Xž¾VZÌ“!A½¬„x@é¨$ tˆÞ!ª´Ò=Öckµ1ðØÓòFG<ÌÓGè—Í<÷ ðpɇlƒ6sð˜‘Ü-vaãŽCÛ”®ƒóCæ[×ϰ‘ŒxèovD†÷ð õ\XsQ; <Ì-afÎy°œ ‡Ï/[5›Ÿ_à¡•£x 21>àá9?4ð`Váüúðçf.e˜Þ˜%%¶B´!ü#ì'Ûã\ÎÈG;#yß%bž¼GÀC£&x´ÝVÌc1ïþ@0ð0ÌcZÛf¯Ìó'­Ð…]1¨ÚA@C™ð‹9ˆ‘(,m×Ý¢ãÝ»Z0k™`^.áNYæÃVÝpnäÞçæ1ó cRÂtÔŒ<0ò®FÍ@‚üj #‚¹À7û?ô÷°NÝÉgµ«ÐᎠð0öU ã%4:x†CÇ.xˆ)ƒÏk_Ú-ް~”u/‰y™aÀc¶Ï ÛÏæ‡àa¬ŽŒgcŒcà±ú¯ûx†|¡°ž{˜/7¾@ä`á±%o+p®¬Nÿ÷ÛÕà¼Ì²’1ˆÅü–-ððYœØÂ*„Ï'øŸ1OYG ð€0ÈÄ|›-Ú=ìä’Ñzá®m¬ÂQw !@‹~Ž0x´CiUð€8 xЇy”Ø”ªºgÀCh‡ü °F§h½«ÀÃ>.ÀcOñY9V̾-­´Åâ·bˆ—ËeåÞz³‘¯¥…¹XÄŒ/¡E :Þv›ðº¦/U_ò5ùMÝŠ>ºGÀÃ}½eNà]úRê˜.ÿS5ó8xà6c°šš&àa# Å:j>àa+eÂK\³Õý<|÷¿]…kr­ñCJ^UÈÛ ›yx8Ö•ÜyÉ!ðæÁW‡}]ìŸk’™ë8®(Áó‰º>%fÆC‡¾¿™ñè1ö"âws< íGÈ xxìE‚|²ˆmì ›+X¨`Q x`þ‘Oð»Ún8ÿšþ&bS"—ÃÁ ±ã—¤÷s6¶8[Ñt:G„þ®@ãáCôÖŒ,”hÞãÀÞ(ìþáÝÞFÈŽ|(zG'†©…Ñ o0)E ÉXž?yÃ'äö}ÜŽbox@޼‘‘Ïp;0ÉÚ•¡\6ìŠð³0¯(ÏóÈË Îºoα"«Äcp)±®!œc€k:. f‚=,òá ´ <ê Û°ÅI®+>P Nä{lyÆÀ[°Á8“DõÍï+t2ð|®­ž†IÔzi žd>d¶¥ÅV@w£ £©‰Bç ‰àûç:òÆb TÜ€ "Ò“¬ ‘Ì€ØÐ×  ˆmðJ$VtA¿øÃtzSÕÛ¨î1ø$ôEØàWìŠÀ§é ôe=k©»‘ÜkàáΨŒµ‡y§ÃØÌÌ£é"%&&ÝüûiuÈ·ý¥7€• mÙñ Œ`þa@—¼_?_§¿•CEÏû B½Ûw"|çÓI.¶!œÖyOp”2sJ™0ž»<Ü J¬—5Û2ïÈj Š:JÝHiZ°oËŽâó¹³ãß,306!ÚÁ،ŘÌNÿ¤Àãà¾bß“  P|Z53Î,;¨C+ñû xxág”Ç6¼™>®{<#Pôœœ <½‹Q̰u„9L>àa^4ˆ÷ÇBÃJdL¸Ú&|-þyƒOxƒ{…{2a°‡xäíHÒ Û Š -Œ[Šä4à±çJãÚšîx»ºî[à! Ž1’ y <ÜÀFõ['ðÒ·¦Kêð€Ó€G¨‚z>Gx8Zɬøî'àm"6Èô²zÚŸ…Ǩ¬§TM8†Y̧†ìõ› sû9HLàK·ç¾‹1¯®4ð•áÜW–0¡vÆÃÄ‹b½Ll;¼.a¿ÛÉÃã,ÎaE±#s„×)fþ6>àa›A¬°‡x‹@à£Â±—š­£Í$±ê0™h,*ìðZ¶‘Ö®…ÿW“­ðÏpÊŒð N^üIç ˆ¡CÑ„0™ˆ€vôýÍŒ§Þ6Vùþá±Ow+†‡×XÁ3Á’PIðè{ìsæ1ÿ4O|Fx„¾á: xx-?] NÊí²\&&'𬹠Àc:DLJpŽ^^k–ù€‡WÑX'#ØÖ©&ðØyùç÷§ì¿ö!êð xø,ºÀÃçÒ²rc±Ü[|.-Tn—ÝÄŽîþAº´`JÁš” ÎÇ8ŸKË¦Þ ç×9¯ÌtjÖ&ªÞÆï?ÿ„$çÚŠ^ÙÂT¿a àZú†ë,à¨<|ñ+c&ð`Ô¡“б4€˜Tõ?|A˼éº<&q¡AËœ nÈý?ð8p9Ù©íÉ <|AË|¿Kì6Ò:ÚÀ㬠e¡À#4hy´ÇYAËôõ²>ŽÓ<&Ÿ…[€E¶Â?| ‘oÔ 6ÓP„1‰^&±":Œ‘ZT¯ é59¾d6½ä†èùÂs5ͺž;¹¯—ãÀ]rVµ&‡÷ Âøá~רlÅhÑF3R£Xi’¬|fhe„üVmØû a«$tÆôïç‘Æ–x¾Z9-™ÈJ>P³!X— r$}[±@GàQ5’’Gäí×u×Â¥é‰N§ÀÓf¿«hDAÝ;Æ"»ØÃÀ B,pb÷…‚3£Z]—s£Vºp:Öæ&VÃWˆ–_¡ÛÍà{•î a3šd "Jj3ÊšmF^×Sì‘tè£Ö0÷ÞöˆÚU°öŠ~š”ü-lpi_æ%¦M“üf}Ædh ¨Bpúi™Äv¤ä-(/8 ØFÚdlÃG4¢D]`q } @5(‚O)u›íž;é«©î/¡hÏñÀü)¬1™û_qr>éïQw˜-¶ÙÓ¬x~“²­ ËœË~°K;Z&&¤` ¥«ét§è]M×%Éû×{Îô~Y'¿Ñ¢¸‰DsK¢ëmSÞ©¾oÒÞSƒ”£ÀJé¯c^ºÎDM&!ãzu§FÞ¤–×é5µ:uQ'¡ ”© ‰¡Ã¨k3i‘°ÍÈló²•é•¥æxiG¨ ]ðœW€ÇVì73í¢‘†mKgÇ`°&OsksÃ×åE¬Ì Z‘¸>7lKQÔÞe) ‡V§?¿1ïÅ-… ¯ï*?}dõË{*_?¼âô þþê–÷_Ù|ú䆷^ÚøÎË[O¿ºýïoîëýªž2\CúÔЮ‘VëÕ ”¡ 8ǤoGØcìDËÚ6ƒZòƒ¾fŽÄððÄ„tšm;œòq|ÀÃ4áÒÉÄ ¤ñ:'ÿëN²„±W°ë3“‡ÑR¥™­ŒôVU19õÎâÊáî:$Gn؆ [¿ÚX)8†GÌ‹Ž xlCGx†Ìù²† ÞO>àažÆk§Ã³†þ¤®À¨h’Þ¬…Ѫ¹ê0‡ƒ(#váñÀ˜ áÀcl@ìQ²‡(‡ˆÝbþÔIEš†a…yÆðg©ˆkŠI%j]Õ¸¿Ë†6ü($4ðà#¥G)màák•â¬çzx±…B&Fž&kŽ€ÇbÛa=˜Fsä€þ20ÉsûÊŸ|hŒûô?xNü]À”‡§>ì5þ7nOþ÷ø_M}(|ÖŸBg<8åA“2õaÓ‰˜ùHäÜ?ûMþ]ÐÌ?D/|"Ñw|¢§¢<Ÿˆðx2ÊgBÈÂq.ãÞee¸Žâ¨+¨•¶^B»€v ÚV£®0^%¦gxÛ¨hÒõ»Íio|÷ xxz3ñú´‰Ù¼cU8Ξ…‡a†vFbáqN9+íuõ-ÀÃNl'9¿¶•À'· uÜèàwá·ˆÅÉ|&ïð0a®¶õplÞh²Å‘á&¢|õ¤]¦³öÓiàÁ71ò@Ôƒ<íÜòõXàYcìV ôŸšNÆç~‡.-D8xAß×B63J›Fx8»mq5 'û…:<‚‚–Ù&M;Í´hš•ý50sâG²Ò_þìïB=Æ&.š\± <γh±KAøü¢(×Ü๠žÓü¦gÍÎ ›Ÿ:/'d~NÈÜœp—‚—%‹Ý6$lLñ_ë¶"~ᚯ’ÈyñîOm-Š)Žõ qùKïÕZÊô …ò¶z ê6 "@;:µ窷ó_pÔh1Þ« e!µŒ¸¢i´Ì6ïXy„Šsš·"&!– xðËð&…2 8•O´íÜݺ[·0à!VÀn4:(d7ÚAË"¥íô÷.hÙ©Àcýé]ž!‡íLËw~˜ßíšb†œÏ‡£ôWTªv­®]£•PÆN¥¬±÷æ¹Á¾=Lz¦.y™ÌA†±mëu0Ô͈í«ì¾Ko·y#ï€Ã8^ÛJÈìàv?#>`–îkì°Þ.Ù‘'<¦x«þŒµG=ЄîT§›q®Q¶Å洛îÿE7„ÅhD*ñ 3± ëÄÚÉÔÖ»Cq"ØÐVÔ¦]¬¡öìÇZ q?wÿ§o·ÐÕ&±˜” 2€Kìö p |•uõ=t¯7y#xM³³®#;¬Ý’ÙG_),ªV$ ¯AHzµ]^Ú*¯!ŸQçv¤pYûÌÌÞüÐB)›xEäÓˆDÖ`’Ö¥¼@`Ã*ßj([ ŸØ/\É:‹l¨VÈ B0@E›tlÄ8 ²TGÆ«¿ìîo£“{ÌìP™3y<Öõ˜3`â2 <i­VÖ¯±ê¨ù­FÙÍJÚYUY:wM’ߦŒÒp—øÉ˜U¶ 2Þ·$Ê;;hAþb¯´€ùñ^3²BÜãí5ý‰P×)!.“Ý&=é1i¬çÄ?e„¸n.Ž)‰ó(Ž[Xšà~tsòÛ&JsUÞ×"—·)à”R—uT§ÞœœEª´(€E2“(ä…í‰Ý’/›ƒˆA.bļæþ6 Øí“ÅaÍa;†JÛ0ñõöâÙgÌá“ÍH³sœ9{²r<|…íK÷ü‡/΋ձ\)¼™sGðþxÔ”FD„i £ 1wa㊳¾v vŒ´F8ð YÄ2{Ð1TpöÄ´([S†,Û•;ÉŽB‘¼®@û®FN,£ñù¾0Æ$­µJÑ@‹¼‰¬Ä$kà[Ôq¬¸ùi‡9Z§0º†q³2žV¼£'t¡óÀ<ì„tFP5fàá¼²sí8àqNö xس–¹Ô?{Zx½µªþF\ðŠôûFU_+¥ÿòøÖ‚uYArB­NßRµØu\„ëøHωQ^SBN u›å7'Ü{¦ï‚ ÁÓ"ýægF§‡ûG¸Ï,K~eï²ËáŽmÉoxÿ9TÙôµNÞ­Uuõ6Éum2µXO2³tmzD­hRÉEy“NÙBâEuRyXàT+n×ô~}†Ï×Ï{ãĦ’4ß‚èk3ýwUÄ¿XU´mI|è‚'¢¼&E/š’4/!dAR¸Çâ€yQA®‘Á®ž³Âý]¼çOóë=ï©¢dß—÷/Û³"qÇ’èm¥‘ÅÑ.{–§½z`õ©[/wüƒ´%ÕhºM†.Z¬’‹”²F”¢E\ZzÒ±‘vþ2y ÚÁzöH ‡-¶>z¡Ȩ;†uõ<|.->á:°8Ö3•±+Z•ëa·k<äßÉ?ZêÐvYe)²Ëkðƒ<¼ûßi¹@Äu«ë¸—À#lûÇ’óÈM¦#ÝÊn8 ŽHáÓ9ðŒB%êÀÓh)!býó~?:êF;Â1õÐ $r–K‹/¦ˆFèeõ óµÂÚfˆX .~8 xì†*Ù=?4àá!NÄi0MÓŽ²“eçÁ]³5꘻º/JN—çÿù÷c¹ëO¹ÁóKc¼¢Ý¦$úÎN qôœ™ìQ–䲨7Èkf˜ïl·™ÎOô<^UøìÆŒÃkSÞ=¼rI¬ÛÑuy¼¸ëÀ†¢ëòŽXùò‰Mß\¿ UÒ%väƒõ²:µL¤W‰‰]Âòì˜'l`¤ñf8A‰÷#ðØ ›áý>àá)í.x¸µw̨cE5æŒ*nö7»=À eún'0ÈG€Ç^1þê.ž.ü½Æì¦rÐ2ïþºh×3C—sGðþ „ ”¦‘ˆIÔ8Q £ A_bOœõ=°c °cŒà5 Î Z¶ Tf‚y”8V‡ØvXñ·Ž€G>EÃŽ¢Òþ(;€ÖMÐ2ðL­˜¤5 ”¬–^£¨§” ”ªÑYAË|ûƒ™‡†‚::iaFé]ƒWÚ§vʘU*>à±+ò>à±:Ï6ÙpðtØÈ‡GºÀcÚé¶`™|”½M_]úø7†.zÚsî£ËŠ"_8°zsEÒúô€ªŒÀU1»ó#wå-N^øtAÐÜì€YiÞÓóB\ #¼bNIõ_Pï='Æ{V„Ç”¯‰n“~WëºEÒÞ¥±sNí­È œ™:oGyÚÑ-§OîY_™öä£?õ÷zúÜ'Ï]ézO§h§¨k Fu‡Uƒ­-A‚÷_;ØHD'm`ŒYJ–i$|1<|âHA¨ 0|Bd*sGu«U­xŽh:èêñ¬R«ô‚¹È¼8ù¾Dhiwd(h¥K-Á‹§%Éêa²%GÁ5tÝW“ŠiúClP8E«†d„AÀ×Þžl©ïÆÍ{h‡wää31Ñ4H)ÜNûaôš×[(ÓaL×pF”¥ÅW/å>ÔÒÎÙÀƒïgÎhÿ~Ö°”Øi¼±Yx~ˆÀczäˆAÙdT5£Oz¸Äð'ªdíÌ2aÚ1ñÏÈê1q&0ïée¨M¡“êÑ„£´å8'Îf4JD(N ý/ ¶Ä´åø(¬Gô/²Z£ ±Ý’³#‰+ ȃ°ãŠé³ÇýõbI³Q„9dÒ‘"Ú1 ÖikaÖ ëˆÏz•–ÁÈ#ÛŠaPD„ÄẠÔ<<«QÎ;bàÙŽðµø<ãs«ÄêOvY³Xæ"*¤Ø¬½’ø°¢ÕÜÅÃRÍE¤É¸²™¾Ïµð) ‚hà»OѤ.ÂUV÷×Âzeo­N&V´˜4Uƒ­FuE]ÿúêÙ÷NíÞ¼&íÅ£«7­Nô~&a±Ëî-¥a~3ÍybCVèúdŸ•±n;óÂ^Û”rmîþ²øƒå‰›3ƒ÷•&ì,Œ9²,ýðÒ´=% G–elË‹ÜZ²)ÛoKnÀ®¢°ªÜÀõi>U¹AG–'WD\ž±6'j[eæŽU¹o½´gÓªÜø·IOü"/ÙïÃS‡êÎœÔ öPƯtÒNJÑ­ºÝDé/ªûaŸá:ªújÐõU¡rOptƒÍȰ j×4IoTè—¶ÐÀmJmâÛG›\ 1Žæ(\4Ožì©•YÉ»½¶oÐŒÿ±‰äâáO›mF6¶ {DLÕ…;vuÓ£®ÃjY‡•¸Ö\HPØ<CŒhO:ìŒêvT²ÛztfÁ=> 1v«úëQ[:C—9ʨÓQëg‰ƒ0÷s3¹Ìù›øÎ!wßýc«áú•À8Ç¥%t{Íà­´Z/¯%<†hGÅwÊk‘дÐbŽ$BÈŽ©lF> ›‘7ÝÞœøÏ•!w`˜Â÷ý€JâNB®%“gK! L-Zƒ×kôƒÕ0¢OsÖpDÙÀw賤l kÐ%“յ㽴MÄqÉø+Ñ¿£•"ôUÜ0ª&ºQ=â®UhQ2à”œc‘Ö#sœ¼Qß_£ë «Gß#kPß>¯¹}Þ4ØH J¨ª¿K+XƒV¶`iB‚6ÃËŠ6,피•’µQËuÝ` Œz %\¿‡âXŒ…°WÜ•aÑËRàŸzã@ƒa°ÁyÔ…Öãrlan³èÚ€ÃÒ)Ê.‹°˜Ç.ðø‡Q;8Ú@–ÞP=”©‡v`Ê…-5 HG˜®TðÂ~E¯è¹ñe͇§ìØ”sdoåk'6ïÛV0ÃÇõ/žóÇzMösŸã7£"Þk{^Øáòø#K“Ž-OÙWs°,öä†\àX®Ê Ú‘ËÒü¶ç„¬Mô8º,öÙŠÈ#å‹VÆ­Œ;´$foQäŽüÅ{Ë“>9±ý¯ûW?¿cé¶Ù{7/)È òš>òcÏíZÕrîí«óV—$¾sr§âûVJqÙ$G0FiºÐñšzũňy4poȾ¯Õô7¡Óbºl6^4"؃»¼YGÜqfEÇØÃ÷à;K4éšfÐ;¤6Fz%ïöD🖲|Û,±ˆ‰eº”’àfÙ*øvÀ#¤»:_LÎhWæ}§zÕ³"û€Pµk‘Œ>ØØ½Rb3Ì [ðÍÃÜ6xZÝo¼=¶Ìs/‡-$ž‡ÄðŒx@‚£Ïn )ΊMrš˜mt\±—Ž˜€þT‚æEÀc@Àƒ>²Ì'fàá;^b0$6C b©³äQÅv8¾eÿêœÝ«sO^÷×ç·å¥=óÄÿFÌ͈ö9¼mÙ['v¾q|ÛÁª²“‡×lY‘òÒáß^>c”µ£FºdêÃlÔô‹Òè{PT î¬-½Q G­ì­UõÕÁIÐË-/;¯™v±go:÷ xÌ´cP6‚X˜çžrÿX7-ebl„‰àVN'¶]3؈€‡ÕÎÉÀC·\±‘x,°a-Žƒ°=£ÁÙ›)E|¥TøRxîð=Nà¡ÙF‰6øà¡ÈIÀà <–Ps¾ëË$úÌ `«'¶)K1Tù§^(ðjU7Ï‚Àù}¤ f15ÈbؑᾨèÛ5‰¬1 4P²&&DÙ(m"BšV ÜÅD#oB¡jP[ ew6‚f£ÎŒ‰UµsPÇ1ðÒ4tÑ'<ÕáP†‹ªÞFJw™Ò_ѶR†«”ñš~°½á³WÆ?úÓy“ÿðÖK;>9}ô…«×U$LJÎˈõÔyë¥]Gw¯˜ö—_OøóÏOÙüæ±-+Ó6d®Nö.‰œ·,Îm]¦ÿª$¯ÂðÙ+æAî­¾j´Á`#n‰Åú6´¦‘æ©m0Pÿn諤H7 º§†¥¥NÚ¢•Ñ¢“7jä:¾2ÔØÅF¾™ì'&Thw´c{†›¤j·[cÐxôÄxnè‚?‰q H@y»AÝÛŒh‡ºN){(ÍÕ¯z>Ù³)§(Í÷ªöm)ûì½@J2Ã"|gÀ¸kCÑ/lv×òÊüH€Ÿ´hwÑÙ¿R¦[‡·–,šú‡Ø…ã’=Ÿ ŸóXÈô‡’¼ž) ››:;Õ{b²÷„8×q1®cÓMNô|:|Ö£ðgÈŒ?¤ú<“å;)Þå‰TÏñK¢‚${OrÿKÑÇ)ãרUºòrg˻￳ï¹CËf>ó+ï¹Oœ:±íå#kK2—d‡¼xpÅ›/lªÈ 9ubóëÇ×ÙYòÚ±µgÞ=øõÅ3”ö ¥íÆn,1r`40uQð¼ªkêüŒßwÀBüYD†vÝàá4d77G°×ÝrT›¨vòVN¾GÀô-³ßúóÎEPÕý<£® yD+­ÑÉj81qpÝ+G·g„/˜úXYv仯î{ë¥]ï½¾¯,;lYaô™¿=«¼Ýn]¢LßRêÏm-LñŸV»puºYœ[NðÌŠDϪ¢Èª’( ŸÜ°Ù°&Áëéˆy•I^þSÓ¦ú ¹Ï🞺hJšïԼй¹!s’|&Íyüg'wW˜ú;um&åEµ¬]§îV ´½urk¸Ï„5e1¯<»æ½×vž8°|Ó²¤E‹g=ý¿[W¦¾ýÒÖÞØóÎɪý[ж®Lß¶:]\}òëK˜m¤– IÀDÑÚ:Ð)bœ'bIù!NÌ„ñXxtâ{<=ÎQÁì?ÙtôOªò:ßüªçCÀ<ƒL‚“ûšq¹ ]æ-+áˆyî3à1´Z˜‡¡Xy€uteJAZÅê´ÛÒÎ?)ðP—(c·˜^õ¸Ž¨±ûÇ å{ <œaYHxêK D¶b¦»“Þz±ê•g×<¼j×úìãû*^?¾®éÜK8¥ n’‹ò›u(ÂYÕÊõ Ó%éûæStþ©KÚÁzR«Ùj6ÖÀ­+¢e”‡ÄÛÊ"™-ʈ¥¤¹Æ05±^Ñ “׃Ø5Ë ]XíÆrœ>‚ e6ùpšÚòjµ‘˜ÚúDw©vRÖßöœì{ææœNò¾­ŒªŸK×Ê <¬J2ìjùì;ÊJøîCbͦU3wõÅoÜV.äkÖÉjH WŠ0—ROIÔÍè[<„>ÂZiX¹™F$Ž]ZÜÖª§> â ¿áwu ŠáQYø“~Î{\}ñÔ‹›Üg>”í²KÁoìûèÔër6.M†?/7¿SýÁÍm¸ÍPº7 øQˆ)¥D}»Zyó¼~Om_5¢bƒdm©FÄ̲$ïühׂ˜…¡³s–¦û§NO˜íùtZðLø¨8Þ#ÁwrjÐŒÂX÷ܯ´0—Ÿi‰³yÊ“ýK½Š’ƒ§eEÏ“}[ L¢W¶)E@; ¼7“&_' €p&y::êºAÚÕ^wêµçà¢ç€œ8°òo¯î*Nr™úPYVè†Ê”¿ßôÂþÏï[~t×’=s·¬H9º»¬ï«:䘣®Ën4ÁÉA µÝˆ qÏ&z¿HQÝ8” W §=¿{F=K‹~, ùއýÎný»÷+ðp˜ÇIÀÛå$xlP‡IB·srîB Pàá+^'x(«w7 “AÀ¾³lIª_˜ûS~³ÿ<åÑŸ>ñ‹1Oþ¿óçŸyä¿Æ<õ»1“ù· üשþéØßŽ™ô§ŸúÄNxô'ý|ÌÏÆŒ™ðè¿äÆÏúìÝ-ˆp4¸ ­¹Ž·“€§ÉRq×Zœ<@;v€ÇZ_À\¡¨ÑIëT}5ƒ7Ωûë‘ëÊxÉ(oE¹WÊ.”{¥»úÝ•³À6/Z¾{cöáÅ¿³ÿ‡ÏÛ[žŸâá;áùý•¢³'/¶¼[ÿÉ‹¥™ãþøÿ­«ˆU÷Š)Ã%`T–P {/Èo|j’5húÎéê °sxtÍKs=²"fV$ûF»åGº¦ÌHòZµ0ÁgrŠÿt܈9áóáÓô YÁ³Sƒf®ÊŽœå=5+jaœï´ìÈ…1nñ‹&åź$Møêz  ˜G¯n׫Zq#o1AJœ=׎íW’þ¯«ëà0)ÍÕÞ/ëj>z¨fMYŒë´?FúMÅ¡GÉ?)þ{7½zt=àÐëÇ6ß»l}Eâæå©Ÿ¾¢ë¿H™¾S÷¶¢;ú~Wv£~ð»jäûCM9%ìVqÖo£™®®qŽ&«$¶³¶tðð÷ºaZ:Û½Eº«ó-³yƒÙ½Ñ›“Æu-ÙÆr;h®}o‡©¬i #Öåä‘•]¢·?zkïÉ#Ë×WÆ<¿¿übË;Òïàºwjú›€pô˜Qp€s€v€p”·>¥Ô"mÿyäTEÀƒJ6$ÍNð„T° ,ç e†Ì*KðIð²¶8&5|~BàÌüx¯ÿ©!È(”0­"Õ'Òë©›—?¡Ô=Š›@SÝ:E«N)Aµ•ð$Il’ôY‚£Ö÷ÀQ+o‰öh16«/iû[7.MÌŽwß»©à¹Ý•Ïî,/LõKY¼ 3Ö³jUÖª’8¡—­­Z™³¢0nyAìε×»>ýöÒYÝ@2û({ôƒí@Œ¨J3Êe»ˆ£I[ÍÞy1²ª6ðV(ζUÇ*Bã~¦c;'¤‡xÆððÆÀŒ(H˜: œò;V'JxÃÑæ–&w>¹ùuá•ÀÛI]ˆX…›Ýh[ù€‡SýIÉùx~žáÔMâ'·!ò2ëD IðøìÝ-K²ä'ÍX‘ç“01Ò}ܼ±?›úÈ¿Nùã¿NyøÿyLøûÄßøM(-xfZàŒ¢8÷Êߪòøµ¹á›‹c’ýf„Ìï3½(Ö''Â-=x~Öb·´Ð1>Ób}g-x*ÚgZŒßŒcÿ÷ÕÃë)Ó×”ñ"EuÁö_ÿP}ëmÃçÚIÀÃWiÜYÀc1¼s<þVÀƒZGiT}uhæ§®¥¯îmíizð†pÎ[/nÿèÔ¡WžÝ°¦,.9bþÒü°Ní»ÚúÁ'§lZž¸qYÜ[/­ÿæÒ?(Ã¥þoÎÉn^@î*s²3 ­W‰(S;º Ô-@­pJé28.==br¢ïĢȅ(ñ Ƽ°ù•‰‹*|²ƒç¤,šš2V.‰óÊ œ ”åRœº(7Ö-=ln’ÿ´¢8ÒxÏ’8å~‹ÝÆ~)yR_TÝl$åÊV”nkNbÏȽŠOä±j¨:°îú7—>ùôoè·®J9¶§â¥C+w®ËÉŽ÷ž9þ×É{7–œ<´éèΕÛVåï^_k’#\¾»|R]¥t_RZT€~]7Ø‚* ¢¿týF„iÅΧ‰¥±•ÑŽá1cSÆP¯hqððªÝ‘¶–à`_áAÀã4 ÏÀãTà±ëẠ]X¡BÌü€€‡÷xGx”B’ëÑM+摬;³Ó…"<„v@ª?ØQ‘ãàú›ñã:þ¿Ãæ?‘è=tÜŠ4ßÊTŸ y¡«²aùØ–|XÞX±6;hGeüs²­ËŒñx&lþ¸ÿ™¥ñ>ùQîiAsRgÇ.šš¾$%tnQÒ¢œ÷™cÿç“Sx¤_Fb§‰R&vG1<÷ð +hYÙÜ÷ͧHÅP®âfãéW¶ÜV¸gcîÞM/Yö½ãŸž>¾smA”߬²Ì𽛊Îÿý…®F`¡mëÊãm/W¿‚â|[uôÒeo­A.ºñùßÑÖ ë ¾ôšÛgтť7ªo^Áw‚È4ؤ¼q!1hBvØ\ œ²XÏòxï¥I¾0®H X ¨“ê; Æüð%Ñîi~Ó³‚fç„ÏO ˜žï‘½0;Ò%3lnY’ϲ4ÿÍÅÑåÉ^Qîãzª_§T—õ½0µ^Ö÷ÁÁv"àÁèn~¾ûTÝ_K²šäÝÓ“rýŠV„p¸µúÍÏϽñº]²v®Ëzÿõ=éÑî«Kã3¢½ŠÓÂv¬)å  ÛËbö.KxyGñ{ÇV}|rãÖw¿“œ–^þ„’µ÷œ{13hfAÔÂ%‰¾KSý—g„¬Ê ]›¹¾0rENø’T¿Ê¬5E‘+ò"<§ÿQr´Sß×@²ÝÑnÀO“6^0> .­a60E½!¨«@,­µ¯|xjÇò¢PßÚVÚZóö?N=»¼ 6=Ê{Ëòœ÷^=ØôÙ›çÞþÕ£áÓÓ/ïøªûŒAڥ铠”%”ÀÕ#Ø`þ‘ÞüD'«aºŸ ¢Iý5Ä#‰|‘ŠÍ­Zím¸¸’T¬ü®61pRA„ËÒ¿Ò(ϲh¯åIq‹@V¦¥xO+^ìeúÏÎ žÀ§y‹]òcÝSÃfgEÎ/K]”:»8Ö}ufІ¼ð¢(×xï‰â_¤Ô×(i¥¾¬ëmìÁý@‘§˜Y†WN†‚ÈþÃ˨.Ó@ìæc¼,»Q/©yu÷†œgûeQGw.íhÕÖ¹K²¢¶,Ï}ëÄžø`—ü¤ #Û—n]‘ ¬]èøíåÏú¿®ÅM7.QÔJß„‰Â¥˜¨žav+~P€Ç^£ œæ$àáÍB <æaÇóð45*[-ápxÒ#ìá4‹Ê?Yв`àqRÐ2_ï¤Ñ¦Hé0å‡ <¼ßs—‡$ëéšyrð›y‡yÄXÀƒ”‹³,<@†vÄ<ªfŠêþöâÛ•YËR¼öT$œ¬*®;µÿóš7oJÞ×_¯Ó]¯¦nо—œÖ^?{éü Kâgo-¸ZsâØ¦Œ€éÈœíþôü±?Ÿ?ö<&üfá3¿ö˜ô[÷É¿_ðÌ/Í|$táØÜX7ï™}!ù€2]¥l’ÖëúªéßU5Ã2AÄ < ÖÆÑZŽhš¯¶¿söýÅ^¾.íÚñ·×¶_lþÛqÇÐy1³n-Ÿ»é³·þz¬êÙå',¿ðÁ1Ù&¦¢¾„”¸²edë;¿ÿâuµ^V¯ì;K?*‘UUL}jsºÞ‹u½õ†þ&TÕGÚ ¢ºÑ< ˆ´(Â5;¨fî’Ï’H7ø³<Ö+/dÏŠäÀ¬€9~³2f„¹fÎZ‘”µ ,É»slYfä_ŸßqxÛ²Í+²vo(xáÀêʼð²áÄöˆß•/Â…z(Uy/0¬zBë™ÜgA˼1DN–Âf$o²¶ã?%ðÜùù¹€«‡À#¡éš=¢§‰Øy$æ„x˜ýl´¤™[È\I&ÂM©X"ǯ±g¦~ý}(Ж¦Ož½Ýsfðò…’¿iþ[ûG/œÚ·lwy|ÑâùÓ“:Ãø]½ä£#Oþ|LŒÛú¯>ýmOäü?-OõËž5çñŸÍzü?>ó«¹cÿ{Þø_Ìû_³Çý·ÇÔß{Ïøc~¼;ÏEÑ;”îrï—#‹„VB~Z…‹r-<°Ûò&z¤ ^ {ÄÖèîÎɬðØ©ºÃ}¡þäôׯk¹ðê§;´º,ª,; (Í77Ñ31lv°ÇÓ/ì_qáƒçl-~~ßò×­ÿöÊGŠ[u8¹‰¦2ì©3kjK…åDYÝ{tµjÔ©¡Mß·Vç—m§Sƒ§¤ùNΙ]ãVéR²Øµ"Ö³,ƽ4Ú-|ÞãåñÞóž¬HðY—†«+»m+‰Ë žQ™äµ*ÿ<Á¶,Ÿh´*5°"Ñ;Öó™w­CEû¥qd²¡K'­³ŠT$7¼¾…ïTö}¦ê?«•^ÐËñ´)«Çå¸)ê"NÛEsqà›šOíX]¶89bîöµ…‰‹½Ë²b–—$¯*IY[™Y’±viÆ‘+Ž\èk}²ÞV,5É­Gºöglág¹œ€d vF]»IßÎAŒšV»bÒ¶ÙËuæÈ®•Ç} zИzæçŽï¾²wß"aÛˆ8©HÎãÞ“áZrH¥\%Ö× ¤zPonTêÍÜ2€3ò=ö Ž 5Ãh§Ï³‘ÆVØÎ&Ú$®à9t§*ÖÙCuEŽ‹q#O"}Ò§—¨ƒÖ9ÌݬÌe™5M¼f÷¦µ” ±yÞIâ‰FB¯ÁÏ;Bn.ÐË$Ù\0ôÇûë°úkk•ý4ÒjœU,À‚jøÖéz~:MýÍï½´±åÓ—]&þ.xÁ¸soìeGÅoÇŒ™ôÛû_cÆýbL~”÷ßNìX_=oìÏÂæ=døê|Ë{ûÏ{lufÐÒT¿EÓÿ„ã;çÑÀ{Nû¿©úwXüºà‰eÙAÞ³n¯“Ò_ƒYQ;X™G¬¸ ¼€²ï,,£úÒRäïÐ4Ýg”K¤ßŸ¥ bT}ZŽR­I#Uÿ9s¯¥ZJ]‡DSÄ’œn5â>uzY#®èØLGO!W5 õ*&3$¬Aú÷,<»7f?¿oiç/ÛS‘àVâ“1ûÔ‰Íç?8zh[ñÑÝe¾¹÷‡ÏɾC’e7/ z’j؇zu5ZÆQˆvý2x,f:r+Ö!ft 4¨¾¯†³À“>-+pjeŒkEÜÂ’ÈyÙÓ3|§ä…¢Z…‘.Ï®ËYçµ,Ùoyzà’DŸ²„E+3ƒK£]Ëb\ #æå‡ÍÉòŸ‘0³<Ƴ2Þgeª¢ïÄ×öUR†+ú^Ô÷Åäè%Ü6橉g©Ó ž×JÏédçõršyˆÛKÝ_+¿u}'ue`©;ú¿9ÿíå3û¶”$Gy§Åúf&gE®[šµ$?fMe:ŒÙIAžóÆoZ™·ûRøsݲÌ×^ØööËUMo뤔¾çK½¬Mq[¤h"®Ý*Á*˜èlc1´¢Àoc]FƒR93-Ö¸€gø›ù‡ÝEÛF¬\<±Û'ïh±ÕbtÐJc8µe¬êÌlÊ)Ìöb±/<`Ã#3Ì _xyæ®Y0ðXóƒÅæ0ŒpÇËV•~™J¹Xi¢@D>‹Ú!ðX[]~À3„ …uöð¨]N‰ã¡cx†<œNëtïÑÆ‘Ošõ³€‡ØmØ‚‡®¢L„¾µPågÔb°Q+kÐHëµÀŠúf“®I-¯Ñ*ëàOdê×uêûDÆ>xX®~Qýâ¾USžüŸp·§ ß·tŸóøË¯"çv™”àâ9åñ0×ÉÁóŸž÷äÏçüé'¢wöž}icÈô‡Êâ< ¢\}f>´`¯‚ÝÆFxŽvyÂm¯ç<ù_~ã?둌ˆ¹<õgNPÔuTGÙ“Û0~Ôã÷”&ƒ¢?ª-Ä𜣗еýbí.jl€íaâÁWÓx´µxl[/a! ;H0 ]§+³¯°™X@IFw<‘þt^\ÈüòÜÈgw-_]–â3|AjÈæå¹»Ö—îßR¹¾2cÓòôÍ+Óöm-øèíýÒpW_£ ×(Ý%“ª]/«Ѓ`T5“0 ¼o¢…Fx€·­¬=•T›æ e­Q]Oi)ˆ>ÅÁõXxCô@—^4mܰ|¾æžVhÑŠ‘Ïxd± Ǧ×;}°±KA„´m¼?mggœ <8Þ„!-$<œød¶Kˆ×¥bÑJCÊ ˆqóM¾àÞxÈ™¹Ì5#΋aGÁjHêøzپ皡–—–ká±²öÔYë>Úr¨i%¯]À›ìùLE¼WvØ\ßÙðøÌzØkÆ=¦þÞõé_ÎûóÄE““¦åǹEùN|ïõ”÷Wwö}}Vúýy84­´Zvë`¸R¸ƒnU¬ë1É;Œ²nTw©?ñÀwŸ(nŸÃ†ÚuÂtÓÆ µ O#ðX€“¼cN‚£˜—Yº§çȧ8ë³ñ¯=ͧvoLýøª³ï¨Z(®>‰‚Lt—Q÷í%Õí&ÝêÈ€…£dg âó…fï0W,Wk°T1¿Cáfi3¥l—œ{!ÚëÉ‚ðÙëÓý ÃgWÆ»Uĺ†Í]›X¹ Þó™‚Å ’}¦óF»Å{OŠvŸ<;3`üK²÷dê‰rË™»!+¬(ÂuUZ@zðô …á¦þjPBÊ "×±`¨í—Ÿ²3 îèÚ1ÀC(T;Xío(¥];ÐDê6kú«:”½Êð ‹ü¦¸¥úõu•‰a‹&{Nôž7ÎsΓ€=K âÒ¢|–&n^‘³aiúî…«Ë Óý‹37­HþìïGnUúž W×U\Ý)t¼Q `âLsce#ÃÌØ ´cPÕÕµ”¶ Z0iêLš†ÑgÅñxXñÌBo{Z§K¬Î³NžÞ´t§9 ÁÝχ{ñÜ'÷ x„<ìè{ˆbbGbÞÜBÇ:ý‡ùõû xø¾çîXxØåއø~Àc¨¶x²ˆ»Ê놾^B_èï*$ãö±à; ´¡~°ZvóR©Z¨–‰TÒFyÃÀ­J×­•·QÆ/´·[n+™=öç~ÿ¯‹f>¶Øc’Û¤?þå—crÃæVÆy¸=ùß~;çÑÿxô߯¤Ì:óê^É?^:¶>oÑ„_>»<¡éÔî×q¥Ñnűî1 ób'{§GÌM U’àYåZ½°0ÖmuAxBðôÄðY¹)žmuo˜ä¨ Îháš-uŠÞÏ4ƒ45(H M;=¨í‚â"B2ê"IùÁa®uf—V½ àa[ɬ³•ÍÌì©þ΂–Åç_hþßé1^k–$—çFƒ¤Çøø/œ¸ª4qÛš¼] ªVg¯,YQ]”á·scVKÍ+ŠÛ"Í€˜X4ƒMšAÄ<i=*Ÿz9¶@e7jdŒj„7uL𣿼^y^§8Ç<Ö>ŽB¹¯€Ç¡˜C}Ø16æD<+¦Úv7ìÏ_È-A/¨ù¦pà1ÁT©³#”¡Í¾0¾KŽðža'롳ò&ßàáùûÁº$ÆyÁÚI¯æùÅQo¾ù€m+ùa¥¥ßuàqT6Äuèãd+¨…:Ú£ vAFfe³ª¿^zó‚rPDé/áÆÖŸkeÝ_^:{|ï²ÿï?ã3ãO…1‹r{&ûÏΞíúTè¬ÇÂg?ì5îg.ý?ï§q­ú¯›ÞÕ|Ù ù¼¾î}Inã¶døÖžÜšäöTAøüœÐ9<¥)>ëJ¢o6—Çð,Mõ+X¼`uvHUy|y†RèÌ©cÿ멇~Z’ÚÙð7Êô *;,Ç“’¾QI@.­Vƒ´MÓ×"ÿ¾¡¹ú.áҨ쭦[šõÀysè;ÒÆwXÀÃÁ; CV¶Ž\ø`øüóu¸ÔüWJ Sb·¶ö­K?ÐhhFÍXûà­Ö«y¶‘ômí`- ¦¾J'ìÒ‚¹Uæ9úƒÈ@Åt·ž?‘½xFNðô5)>•ñn«R}V&/*q[“°$Ö­4Ö½,Σ,Ö€''|~VèÜüHW‚ð¹«Ó}³ƒfä†Ì*\˜å?£4Ò­"λ<γ zAjàTíÍJ{‘ÄWã:0-l;óÐ&nk.B‚.10޶‘r‚p9n~õ‘AÙDQÝpQÔ"éÍZù­FÔ÷„úzà»ÆO<¶oYqFp|è¼ü”À´hϲìˆÒ¬pÏ9Oú¹>U°}m.l°kc^N’gYNP|øÌì$÷Ïþ~Å6ë¯âï¹J»õrôŒòÁš»®4 ļcTWT€v´òó£<ì6lq𞱆v°¿¯ÙÂ<<àÁW"•“JfÙ'Ÿ…GpABá»bâ¾óÌ—EÅ›]¥vèâ¹ûÀÃãZâežÁ‹+ÄìÕ2뇷~Ž+í~> Òh©tmIf¥åÑsiñ>àa©fFÙ!UÈ<„v¤õ9úZn5ªûA6èå¨Ô?†VÖÕÚøÎ/m/Ì MŽöˆ œë;wœïì±Éó‹b}Wd..Žñ‰rï7ésþI–ï¤E¡_CÝnÑ\;{£ùÍïÊ/~ú½èôÁŠØƒeÑŸ>·6ÇZYŒ{fÐḬ̀9ia³‹“½ób–§,ªLñ]—¾<ÕoGEbY’wQ’WA’g¨×„Çûo¿ù÷1ÁîSÿqêYM/(ýk”²K;Ð;‰ÓÆQü°^ÚBÂxT½ ÿ8]uãó¿Ënž“~VÕ‡ã{íÈrgÀÃ2 IÚ!% ißÖÏçíoÕP¦nÊЭ魑ß8o’‰ù([(},¥M”¾Ã$kF]±t½CBŒ€|Öê[…-ÃíÀƒBЕp8µLð¿$Öumꢕ)ÞKÜ—D/,Y¼ 4Ú5/tv¼ç3ùó×f…®L ÌtÔÉ w‰pW¹ ?lV¢çÓ ã3ý¦çÏ)‹r/‹öX‘âW™êë5^õ]-¥éQ÷×ÕBÀÃêZ>1 Ú!ÃÜÕ8’\×Wq.±ßÈ¿iè 4ØÿíÇò[çŒA%iúEê¾Fœ¼ße”u"k¡æjgã[¯][šT’T–â5)=Ö-'É;>lNNâ¢Â´ÀS/U½xxuLðÌ ÿçbÿ©+KcNZu¥íƒ¼Žp³'xL4ƒM¨;˜F ”…{W5ò1ªëõÊj¢Æ&J¹É x#5Z…ý<(Àƒu7 >ð&”NB¯ù€‡U³…U ¾‰§á— ª¨<ÚÀc„“ HTv{5󛳀ǚ7,Ì3ÚAË<ÁÃÃÂ<|Ø£ “²ù^”lƒ¥ÕdóA&P™}EœèÒ´Ì–|ze…`ë+Èa¹´ê Öéêuý<°Ü€”¬¼w5ºHQ_À+mOëû‡v—{Î÷cÆä¦„”dGeÆø.˜ðp˜Û”Ës6%&xO_8ö7nOþ"jÞ“Kc~[ÿ&õ}õ] uýl_ëÛß6¼ÞüÎÞ7w_R2#Õå‰t÷§<Ÿøï8×§b>•â?-~ѤÔÐYóþä?ë‘´À©~ScÜž*ŽuÏç0eCeRflÐôqüç˜1þê'Ë ã¯¶}Bé¿„}Sõ5ƒ®Ñɺ¡ Ôx–ߪ¦¨KHÓ;qAdû².›@€§Ñq …$YõØéèTYLqM¤/ÏT6 ~su¹R4£ôšÕmЕ؄¼NM°^úí§”¡vŒ”æÈArUZæá±qbÖÑ÷_A™z°ÿ†JÞúþ+}¦ÿzm¦ÿº4ßåIžÅ‹ç†ÍàÉ ž™ºh0jnØ\ÒT"Êm|Þb—Šdÿ܈k3s‚§§ûMÉ œ^:oe²ßš´ žuY!krƒ£ÜÇÉ¿¾@);e7/è¤"äÒ<êþjBwÌ¡olC¶qÕ‘ Àƒ–QVÊ‚O)m+n’Þˆl2R ÇÅ­zåíToY{‰Ò\üþÚÙΦ·×/Mxì·cB¼Ÿ®Z“¾~iRAš_˜Ï”¬xoÀ¡Øày{6•¬*M öœœ6¿² ìÅ#+zÄï"ò1~Ni/¢’>šN­´™¾±¹›>ˆ·ûÂÎÒ ,ðó<4 xhÚ‘‹ø›œòYx¶pVË ÀcP·Ø :cÖåž,uŸîð03á]ÓÐÀCô©3€‡“ÿ#ð0*¶m‡¼Zj¥Õ÷,-}¤Àæ~àiÂÑ4êúˆ ƒLBÉÛàýWu[rþÃc—§ÄG,ˆ _X˜±jIF\˜GˆÏ였ùaîSc}geF,Œõššì7ã¹µõo:{bûÚÔE/mÈ:¾:éÙ1>§xþ¿Û׿å—Q6ªÇˆŠ{´Ã4¥“7‚ÐõÓT’ïÎ"5§kSöV#=ˆÃ]¬ëDàቲÊÏbMê$´I›wGaEâ;´ðàИ'3—KR4˜…362ªŸ¨uÂ?$Y›I.cL"æõ+à‘ÕÂíÀƒ‚–©‹Ÿ·¾IQW?xmsÀœÿ«ˆwƒK¹*Õ§ lÖŠ$ŸíEQË’¼+â=rBçĺ' ¶xBæ>ž¶ (Æ8'/tfvÐŒdï Irg•E¹WÄy'yM,Œq šóHó™çˆK‹2õ  eU;5ÀŽM•#úVÅí ¤&îÃÕÌ4¤Ð ÔЉùøz‘3ƒ , BR€ô &Ü…R¶àêR"Lõ¸^“B ‚®®û»kgš«_º(9ýÖË›’"ç¤Å¸¬«L¬ÌLr ó™꺬 a÷†²½›*«Vl]•S”°¼8 dMyüÅÿŸ½ó€nêÊö>ï}oÞL¦¼™ÔIB¨¡Ú`ãÞ»l˶,ËÝVqï²Ü{Ç`z¯„tBH ôâ^å^0½cã¢ÞÛýιW–« ÎYoÖÚë®+q‘ä[Îù]þ»å´p° QÞGG€ÃaäKÔ-a·Èù­hÚ˜ ;C5“ ‡k¥à÷HÔJ>JQ½\¢ZDÙŒ(˜2AµVA3mÍ‹ø'ÇU7üÁÃɆñ‰&ü„ýEj }¿gôóG~†Z)ZÖ13›i®ÎHÑý(MŸ•­%ÍF+ˆšF•@žk¿B Y£·ƒÞ'؃×g 6Ó‡´¦È%›˜«®ú™ xþ%á»±&¬ÿuòƒ¯ÏXNÃÎ$V²1ãó#˜aŽÐ”Ÿ3M`Q{ÏDv…ªeMÓêÌ€VL7b“&¥¼–ºXÍ2˜&xÏʱU Z¡‘ `âᘃÜAd×…ÌG×EØÇPœ×æFg2ÈÉ1þ‘dœÕŠ„pŸ²zArHJ8Ñ|éÛÞvK½mï_¿=¶>Î×Ït®õÜÿ¢Ú.Ló7ó󞔯օ•„Zoc¸7·éÊÁ‚ýA‰x½Xç!¶KCíu#ðT=_»%A8Ý`ç°ÝÓ$ÊÝ(ÞË<ÂÝ0ŠhŒ×#Ù/ÉeøKY·õm+ËZ6ïø†‘îûöæ7Uño Š{bn»RÜ)æ1Ù刼ó$`Ú8bVïÐàÄ€ ÂæÚR_¦ŒŠ™š>eê²#TY%œ®’EõgºÔ/å]ðåxõZ,x4[oZƒóø¯žI8O¡eéÜG—OÍ<­:ÿí†øÓd˲}Í1ÎÓ0ÉÇ2/Ä%%À&'Ä9%Èžáo“îâ¬ï ûÑ|Ab€C‚ŸM Á(-d—H²LöµÉ";çÐðÙ4—â/²³NãňìL°‘vˆØU3pÎeí/xš0ýL˜5§Éa¢OФ˜„ƒéùÜ5·×`{ಗÁaex.5ÚŸêèCv_—¿gcæá]Eô0w«U'%7)è“­Ùכϰƒ›ä"¿Hï"›2 Ÿ· Æ¿T7U¢v…`Äá G“Wå¨aÅ2xäMJqÝïxÆN»cû“þvÀƒ~EÓë<Ú“Š_í§ëþ ÝS×4eQÀÿ>ð`Ïøô™?ÿžç–­M`žß ðŒ›Ô4À#n|YÀ3Òx¨›QAZMsF¦h4‰À“Ó ¬2B•Ú[ˆ¨pÎuæÉƒÛSBp1dÜÊùÇÛè2½)>ž8“°@<€õEŒ‚ŒðȧÕi”C›Ó¾Ú›Ÿæ\–âO÷2ˣⶥ%{›}ZÑ{é0ò°ò§ÝÉŸæmJÀíIó:¿/ýhah¶Ÿi¤Ýâ0ÛÅaöËi¶ËÃ\ôC]õì—:éæ!šÏvX‚[C0¡9¯ u3 »é¬†ø˜#Ê>ÖÓv ç"yrùÌ1?ËyïýaöÛ³>xsVb”×ÝžkòXÂí’ :äoœº6%Ä3Ò‹L”Ðy®Á€±yéÂÆ±1Ct«&{g4‡gš*-y—#íœ#C'}øãŸàQ±«N 4n-4^VûÀƒ%ð`À£Éáó>˜jѲtùpmýŽˆ¸ëò[dKH/dûÂ×´@ëd_+`x}º7ÑÔ`<Ç®z˜Ì¥º$9 ¨cHòƒñ¬µq>ù¡î ^–xr£Ü|m?þùóÕˆ° j3J;Äجs²þ˜VæA†)Ñy,_] <ì ÌM ¨p™ZŠ# Þ ´S u-5]aB“‘0“JÁñ¢vX祺3ü¸F8ÔzñÔÞÒìpF1*Ø™êmOó\•’›D¥zÛD¹l-MÊ¢¦Æøø¸å§Pì.®<÷çI;"ïC¤OÙcÀ<ˆä·¿nàA¹‚ß(z•Âvu4&«™‡ ^xFÛ¾3a×EÏdSɺ&Lõÿ­G›P¤V†Ÿ‰aýþf–Idª‡Ÿ‹.l…<`4Kù7T’ûîÔ}sl›§‹ÑoÍzû¯³Lô?Ø»=—3h穈Û%nã Ô j%¬F¨½<’i#åÔ¡ÀÓ¨ÁüÝ Nª´Æ¶}NÄJÖ “ZÇ|)ëן£ ðLƒ=3˜¥3<¨ø@ª:X+¸**GÛ‰vžV Èíï?Í×´0ŸK%ZYû,²c6Å L6 —†_C÷-Œ"à!˜ÎsÖßeÕì(¢YÉ,=Ø6“âK š'ûÚäP]óÃ<2)¸Tªƒ—弃›â~ôð(ºaª† a7<¼ÛÔÀ#FEEͨQË$à£A5ì}£5E¿F <Âj<¢Œy —IXǼ&ÔæáôW@ìmžz•‚nñpÇð£úΆSß~¶./9˜êmà'/™’í 'šì(‹¼±(±,/nuft•àãbN&Ú¥EþúÀ£ÞZDö09¢z€HÀââºp¨YÂn‘r›ÑÎPè´«è@XÔÃYìwŸ„äYQÈ– ¢õ‚-Síu"=ŒÈ¸~öKðÙ/‹r7 Á­ Ã酹胊"™ù9-·7ügwërQ\Ô+`w9ˆðOÿÍ®«ûwæ;Ù,ÿá-™ÿgGë%ß|¾‘?QŠn#ò›`‘®àµ@ìa×C_Šê: ­Àó\³!G5Xj„wÆvÑÒâ&NðÔê²NðR5VÆŒ±ÜzµqêF Lë“ Í6Ÿð(Øhjô{À„@»Éà5pʆ«A³ŠÓ„ðÛ>Û‘¼9'hC’oa¨3žô ›T›¼—¼0×õ ¿-T xFs,>þkf(¾,™îaLsÒI ´.ˆpK ´t5ˆÂfãÖÄù®Kô/Œó qÓÛVDSçðöÔÏx$í¼þj _±Âs´ßzëxà©føS™¦<_;®ï ôÞ7ÃXŒ `s'ìœ%h ÕˆX P~GÑ Ó’Å½2nÇàê[¿Þ“›€³øØÎxî¦âøãÊŠ3ÂÒãöd%Ëré[J2²é¡4og¢£)Î\‡Aü|ÿê{Ý—“ò ?ÈC˜)³î™`¾Â0¸²"&"oý7ðL<šeÒVo®O6•¬{*{-GøÂ6sà+®2®!õo =Ø1ÄYßtÎódSœ>- ÿ¢$bºïžÒ†Ý]7—bÓÀC¶\b§‡Š[áo·4ØI—ì_X.ò6™`½˜d>ßÍh6Îès¿[¿ÿÓ‰ípŒ<xR?ø´^)¹%ö‚ßÓR2:ÔYoÙ?Ì f¯Zþ^H s{ÃÁ V™Éz¸}UîÁ¢3I;–̃Öasœ~Ñ4À3Òe+‚½-f<˜o†c0º£®êÒ ™1À£b×{YÀ#gUÃ"(ð(ØU²ár¨:ˆ5›“6ÃÎé°X‡’Í<¸9aG!-'Ä);Ønm<0OF=ž ²CÕ);Ô5â´&ÁÏyÅ{Fsþ»ùÜѧm—|¬'û['ú˜a:<Ùç’(b. O'Y­MðÍ‹v$%y"’ë>óˆØUÒ¨ž£¶—u¡1ÊñÀS3<•xøˆ š° š ššv°Íh'3ø¼4K¸52~øyü¡r8Ã"×Uâf(6Ω× 0‰Ós+rŸ÷Œyåô'¥94@>±T—ŒxßOwnYœÌ÷Mñ óuO X“ÅØX˜šFòs3q0]Gsݳ1½æÒçˆä–r(oCU´W)$siÀ­IÙõÿGCZ£ÀƒµÖ±§mæÀÓñzð7ž)ê°f<ϩҚx&+<É,zŽÍdbNÈî÷dOKwÛe_^+hEkOî ÷Õð‡›„ì&)¿­ç½>tç2ÁzvV¤ó±-Œméþñ½²¯’8 ÞÈjáÿø˜.Ø•FÙŸIþ$3ðëµQ_¯‹]ç–G³ó7›…[Iµ†yË᮫ç9êP]ô(Nð%,W·]ëa7 ¹ê'ÛÅ‘íÿþÇYsßÿÏ*®«å `© køY-¢º·ÈyTuõXh°ƒî¢7u>~ÓPçÝ ÅaÌÊÏ%*¦H.Ì^Fn2§žéµwF*|±„pˆ”3 i©®ƒ¹R)láV;pêT]ïái€Æ©GØu«VmØ›Œ7cà‘ WAàÂ6¸rV¥tèš‚ x N2x ÿée(<(hÀ³cuX$iU´ÇªD¢ñê¢H<×’(¨:x†áoSEؘBvÒyo8[x¯žs³ÆrÑßÒ‚ìR­¾–toóT»ªKf0O&—L¶ '¤„Ùà ƒßÖ$æ ¢:c•5 ÌZGÔÁí«žÿ´ôñ½krKÄoäsêÒ6§IÂï”ð{ΜÜççi¹lþöfïg3ܯœÞÁz\31[~`H‹‰1¦zŠ}=/KY"”4cÀfØ'-Ë:±²,1»˜ºhkBæ½A=<˜{Gɪ™Þà®ĺØs¡t$ì4ÁC PW†t¨R<²á ÉàU x`Î<,òmS¿l¨á¶ÞÆñÐ-wÉ ´)Á1|Ìâ=MŠ"ÜsiÎxbˆ¦ký2©.†ý!ÔÍ{«ºãò×^ þVY!ŽY!¸8’)¸pa.ú ›,ŠKN>#G´N‹p³9*XÄ” ê1àQÇ’DèãÌGG4âð»E³Ä9O+1°ÉÞab>L §r<ðÀ–’U%¸G €\/"ÐÉhÙ{>.¦¥YqÀBH¾®fY d²—M0Ñ:=ί8#¬$3ÔßÃà¸45–°okÊéï6ßêø Õp¾‡ˆ;¡7 •µÑèùŒí€M:З‚†B3P`êFc·ZC„#ò}ÏïPÀÔ(HOÞ–ÍÐ@µƒÀ¾\P NÔo<Ð:,3GÚý"[uÒò”âÆàΟÒÐF%µ*$3g <"Ô„/¸E§¼ß)ð`ÝØ-Ðd­yP,tPmé©7i«VP™´U?¿“¶â×xD/¼`=â'm±.ðØ1¨šôè™Á‚ò¶ê‚”I[øiµ“·àÔÁ‘~2¦V]‡•uhý­ß[3ÆÆëðë'C­cá¿<£ºU`BQ¢Õ@0ë@ØþèÆ/—ÞvdO*Ñy™ùGÑûì?z8ž`·Yš罩˜¾emrR¤·Áò÷‚Ö?ûÉý®jDöQ>ôÕ±TûkåÜfÁ`õðã+0ó™WÏ껳b7jÏîÕ›=+)Ø:Ö˨8Ê%ž¸jKZ`r°£Í²wôæøÛê-|cÖ‰­9GŠ¢æP¿[¸€öÍÆ„õ îQ.:aöËÃìu#qúTGÝÛ%4Wý(‚1Ù~y°ÍRðf€ÙBª ÌçIô³fÚ9Ïß¿¹x[YÁòþüÿfýýY6æï_¹°G)k—™¬ÁJ™Œ7ä¢^•äîÎKÛ×§àm?^±àú‹þ”ƒo¸zTÉ«¶ ^+j-X!ùð0Õ™9ÚÎ?|lÑG€xfZ–.á4ØuЋd»E JI‡JÚ®’vÂv<¼úɦѾÆj«UêÄãõÒ“éÆº“`>ªøH‚:zäæPÇ­U WKžUÊÁ‘íë²ü–F¹PžÆ«#Ý’½-Òí Âð¹¡.éÕÊ s³]ö&ÞpöPoyoÕIóEM°ÚœCl“ášá⢗àc™ìŽO¡Ø'‘íȈ QtKØÏÔÅS¼Zõ/ŽÄv±},“_„>éØß(máö_Á„Ъf,(&åVÁ~jœJµwH†e•4b]bÁ„-úĉÔKf€\}š¦Za¡k+,#¢N­Ý$+V‰ªÃ ÛaÁ£ê."¿Swùè¡í™_Z³>?2ˆ`æo—ã—ŸèeMt1 Ä¥Äùo\XV·©”MsIˆðذ:–âg ì³O */}† d‚.Dyð„׆¨n‚›G&lƒó&k¨“6ÊDµ*y½L\ €êi%í †!BØ‘7! &ÜbÚ† è¶‚­EðîUkS‹[Ô\µ,¡f '17PiÛÎØ˜ÓµÜ’´Ž´KC›CO'"ïDƒ€ã·ŠëˆìôÀ}i|Ü$èšb¦!<˜ =-¦ëí53á¾ÑÎ"L ó„ ` eBÔ$oL‘ÜõRlŒbϨëUÚ¤5—lz>cGãÛÁ\¦àÂa¼ C“mêw4ûXGx4>(¾W1ÜjÔ{9nº× ¯—a3;Ú‰–fc ¨ÃöaÙúŽÖ[³…’ãGBÌ1+ͨF©º¯U¼J¸½3a ×¶5/ºÖhý­Û*µ ª5Õ¯×Å“úý¡ÌÈpÞ('kJÒ`Ú‹ªÀ¦™ƒåð%Ò%óieV#Àn_5l«<@ä÷Z*¿ÙPU’Aݱ6©4+lmv”“ù"¼‰nÅËbSqü¦¢¸Ïöx;¯LŠrûáØúÞÖŸ¥¬Toÿ&üQ;ûþY%†ú„˜”Ùµ’*LÁõ‚þr0¤œù¼(ØqaA¸Óê(—X÷ŠÂÏ]è C°\B°Xæf¼Àtþ_¶¦RGÊ£Î#ïMó9°'ÝlþQ¼Ë*ªõÒWÃx/ó5tßìp70Ò½-3²h6ËBít’¼, # ù‘¶+Þ+N‹@Tìë-W¢iø÷ßœõÎßf9XͦG;±úkEœ ¯ƒ7Ääµ)E7a>³ì³âǨ`Wy³ÐûÐÞd^~jPßjy¬t˸mbv“pzZ°ô Ápú¨‚¹ Q¶âå \±®Ù›7ÇM‚j½£Ñ.ZøõóŒ7¨ˆ(lUJ;e¢v1¿y-†’‹ÁSß ÞA”ן<¸,â“ ÒØÔ%W‚†‰u‚Ïu9rµú””,pL3"èÀä´(Ýß*'À&¯Ÿ`“@0Î!;àaøB†¶šîçf2ÇÙàÁýzÖíZ{½÷bü-¶„d†»ÄùYÄûZ…{eÒœ3¨¸tšsz(.#Ì™Nµ’ ÀÜ'<Ð{†5¹˜¬ :šÉߨö>©‡É}v #´—h6øƒ§00õ„´Ó6ã[ÿ޽#ãUOïRž¤L‹­ÜkGâ {Äà °Ù¶uO¯¯©æâ‘³ßíÌ“ãMö±¡úÙ%DxƆºÓ#‰_ÛäXð~~zH^„7ÂÛëf$æ¥Q"iΛËèUW¾xö¸NÂïFû2a·TØî!¯3\)ÖKDµ Y¢dJÁ/‘6æ°®ƒ%Y£Br ¯\.¬‚½½äàön„[9Š@bõM‹ f¢8 vM¢›iÈìåÏdæÀ3¥É»Æ½T7÷l›K´å´L¡H3Ó$^mN’ m4xðG«¡ “Øj#æÃ—†."dÐàš Ã5"l£]ìá=ÓŠ¡&°&>(ZÀ+a5)x’oÎyÜ{õ»#ë²ühîÉ!»×en/MËŠt·´3\@ñܽ.ýÀ–ÜXŠóªE õµ¸ÑòóýîsRV š–ÛLÃ~ClpVë¹÷Ï à[ص0‡VÈD†kdƒ•hÚC“LŽâ¶ã;¡®‹·${mMòJ÷·Ø›º¿8ÖÛz)Þt¡ƒ¾¯­®½Î»[Ò‚ÙŸ´8lgq'Ãcº÷öD"Ùlv¤½ÅjI¸ãÊ`»e1DSª‹^´‡q„˪X¼QNÃÝ4 §˜'ÚÍ(ÙßÖÅðÃGDÖ‡ 2Þ­Š‹GC­ tþ¦·ôÏ´@³ý;Sܼ‚Ö­?–ñ¯ Y]àDøHÉ}pæÛýKÃ¥o/™ýG'ó…[Jã¥ì.D~Lˆ¸BFB˜&*ª³Ë¡@Ù‚(šTØ%S5+U¨Õ ¦îY ëƒf<`ž’K;’N±°•5Pãq¼f‘ `xÿÁóÃ5SF;€âîK»Zɪ’UAàá·bÀê®›Š[K\Ž_ã™âc™æg]C\—è 0&ÚÓ$7’€7þÏàŠ¡›Õxˆ6 ‰Id;š›^¤§ Ýߦ$Ö;7Ì=ÁÏ:Þß21È&Ì{Õ“ëgÀÙ€”f7~ÃDÔÁ g†Àƒ1ì´…:O30jzÇ<¿}á¯D~]Ákt*Šo Š»à©Tñ¯ƒ›“ÛÇ<¼;'.ÄɯOp\(šŒ‹¡8ç0s“Èd/+‘’uY^ €ŸkI¾ž&DüªâÜÐ göw·~ö¸¼”½Ð!ïæ±êYà‘TtÊ%ÍìþËR^5– !ãרu›e`YÔŠ*Â0?J1Ê?°ä©îõ ï@>PËbÌ =!óÏsØaytXpY:mî͸>浿nÀ3µKDÚôÜÖSsÅ ur~Cà™ÒÉ#)²Ó%±äsMå¶?¦¡‰FtìR¼£µŒýuá(óŒ¶}oNQ²4²Ð›ÒƶÕŒÑɇѥª)æUVLT FÉÈH"mDmð àÁl’ñÀZµfÁE„‚ŠªN uÐ^ØíhWD¨ˆF¬:`'DQXH>»[þÙîܬÅË,—XšhgMVtIzdt ;ÑÁÀÝfER8±(5¤ …¼±06—á¿¥$FÆî„2øåM%¿º‰xMü*<`äÜ;€áø:n½j°J1\ PG:\¶*výæl¿hOÝ]™þ¥‘¸O‹"¾Ùš¶:Þ›†7rПíjºÈNçŸ:ïÌJ ™U|±ÏÖ÷Ï‹¨?m¥+ £ãW0Üc] ãÝMÂ\ôc½ÌÂÜ ãˆf±&f SHV©DKð¯dËÅ4]<¾vK©žæˆjʸ ®°R¸×ÛòKa:ÙÕf‘Þ¢¿,Ÿ÷ßÎV ?ÿ¤˜ÿ ˜÷ÁÁˆâ0Ñp„=TÅ 7ƒëR¯ó‚¤Z.(‡Æ¯”ó«1W›º+“6àÑÒ’B¶™–`+á7C­4Q™k¸¿RÌož2)Kƒ:š&ê䢗åáš„uxdƒÕ*V£¤¿6/Áêºl}¢7@ªÓšhdo ŠÝÒÔ@Û Iþxb½-r¢î¦sW½ÿóÑ ç¾Üîï¤íg¾>+¨(Á+•æ˜ì¬0Š˜îE4 õXF0ðÅ}ÜVqf§ðÑ&‰“#œ†7cµš±—ÓÏæy‘¤Á)zi´]—ŒÓ žMñP‹‚ žß2v»„ î±^•°QÜá4Uœ?´ws #ïå¼ÂÏ͈à ko² %Ú;=Î/3!ÈÃiU4 Ÿ™¼~5½('"9Þ7Ø×*ÐÛ,ŒlpoÎÏ?lï{Xæê?¬BîI­pZWu©D!lñ딢F¹ VÈ*G0h0•â:™ ÐŽBÓ`ðKÌm_&íêXá”´3ú7þf!­éÛ«i&Eso¦žñêåjŸ¢ãUϯӱ™x¦¤—<“% Ç<3ž ËöHÆ0†v°Ô¬1 K£1ÄI?±'÷w<š±kœþLã¨3öýi„ǽ_;Àªå°ÀÕÎÜ3ó’ CH;5v¤#ÊK0Ç@ýXX³fB˜÷x¤ ˜ã`K8Šv…˜p¨†Û_jÕvÃõ£ä¶d¸½éÚ—û7§’p+V-ú+=Ôí‡c›O|¶%?‰èn‘áSš]œµ± ÑZÿ£m¥‰'Žn¨øå0"º¨+¸Ý`Àòp°¬IEõÀÀL$å€ÕS1\ g¸ ºw öÔ(*•,ˆ:¢J„Û¨dÕ%¸D9. Ë 0Û’ä³%5ÀÛb«åïâÍ;®üÐ`öG¹D¥žÉ±­™Oýdà]Õ Û°ª" æ(ù0Á…±êÀ…†åB`µ;­ß–ó:+ŽþI!¸u-õ?&šLóS¨k b³’(d_û >9Î?/=,•î_˜ž›Nó÷² ¸èÑíÒ¾‡÷þòã^¹ðtoŠº¡çGÞ#á4/R‚{RÜ,æÔJyµr¬¡€²QµÀ­ ¦£Nã–‘†•Mji&šº1V£Î\´Ÿ{þvÆöBòæã˜§uJIðFB°X‰¼ýzx~/À3&˾ðŒû±¿SðLE;hšz£Æ[;Vdøµ›ßx¦ØÀ£¶±ô™Â<<àâÊÑÀ–¼Y=V1 ¸<Ó( 3Á.Ͻ°jUÞ hçü‰]G÷å$ÐCéÞÙÁv¥1Ä*®0Ò=ÞÛ,9Ø!ŠËŠpw5™ãiõ1Îx6ÎxNVŒgN¬gÃ{S  ’ß É”½1[²B×§“7eQbƒ,N.Àƒ¶s…EôãúÊM^x4>àÁÞ™`FkÆÄ¶^ð4aáNX¾µ.»QÀ”„ÑR\1S̪|xæàIÚ]#’n¯UÊH|Pº‚ÛÍïgv7JŽ#2¢‰¾Æ ³“ÉŒhfɱ~I1¾`Jܽ)7>Ì«47nun ÁA7ÐÓ8›á÷å¡’–êoTc+Ø· f©ÁVnÂáZþ`5TüFo]ð«|ª«´D°hQÓcnŒÕªëÝ~cà™†N§šm™ZÊÃ[¦ ´+ýk<êÌ–¦±€1=Ì(iyÆe¿/ x´‰G©&T©èUNHvÂT+Q&<Þ Cu¿ß¤eÍ 9¾ACÝŒ“–kàÇ]uêŒeM\r„ca¨]ÄñF+¦Qñ|0NvJ†›¹O)Ýä>ml¼z|ߦz¨k¨¯Uj´çº¼(ã 4Âæ"ÆöÒÔÒÌ`k²"ãi®Y ~GvÔ\øâÉ*D|Q=E„€”®sUɆÁÍÖ‹È®#¼V~_…ðY‚\GT=hs“”U!çTÁÛLP nH%§Šóð„] KxP?j¨U†©“ Õ¨ØõO:ð±|ÿÈêð›·&T~µ5-ÐráŸýìt¬–¿ën±Ô×V×iåû®+ß¹yùèÙ½Ym?l=³#éÊìÛ­q ±ZëjHµYFuÔ Ã¯*Š#eRpQxC€@‰³d‚9Àžx7ãô@‡‚8?+á@"yŒ¨Tü?}µ‡äl4ç­YºóþNpÐ?ÿÃþ;ñmÈ<âN §AÊC•cEµ£…dPÓÒÒj\Xþ Î3ûÑDÖö…ýWÁ°,®j¯9ÔQs¨µêÀ4À3Å"÷%tàš’U%ó/˜…íý½g£üŒBœGÍ ´¸sõóut’óÊwÉΫp†s=­u(.FDóƒí–´ý¼ïXIxíkŽä}¹:´ö‹2Ã÷ÝŒÒ|lBìtb&¥ >§—í+ˆŽv3* 'ä‘ÓIÖID p@Iq=# æl°ð·§BYQà0PƒÕ±` –ÿ¬F%hçõ¨Ö¨Ü­=u|Xð.™ý‡ùïþ‡­Ñœ=Óa&³j@ι-¼þìn£x¨ðëQëÞMYn¶:ú‹þ¡3ïôxRcù—2n"»¥u -V™{P«E­zÄjÕ~žIÀ3"l8ÎT<Ô_Nõ`E{õÁþ[? Ü9=tÃ÷Ïžý¶ø§ãy¿|S4³*˜™ö:i΅Ű”ìJ̰*-øOxmOºO§„Ù%S­sBœrCœ Ì2Õau´gIŒgI1#—FÅ¥…8§‡»yX-ôÃéàõH:Y1žI4§ô|b}¬¯Uv8!•â’èãP–”Œ+Iô£âõðì^ w”|8·ªËÒ_\™SÊd==ƒø¨n0˜Ò?åŸiHK[¹ú‹¶º™#$œJ)· ûêñæcŒ‰¶ýj7€Z1’[‡=zˆÜB-ˆ°áƒÒ¨lU<ˆI"V£˜þÒë°u)«µ£á»“_nض6>%Æ3šâìäj½$-Ö71Ü3/™Æˆ e%cÈ„âôÈe™yI!Žf‹i>öI‘^à€ÒìðÍ% ŸîʽÞ|"=0ÙDv“ÛW%ÀŸ0uÝw8 ˆ¼‹Û –EÀf|-˜p7t>«Ú°s‹ÍVdŽÊÖ(iLxó¿ƒs¥ý@?AS#ÓtÂ}S«ðMÔ̧¥3^1ïW7ÇÔfšr¤ñ6Sá>­¡®—5ÑkSêþuŸóâ€'GMÖˆiCa¢ÜÀ´kwkùœÿ\™p÷Ž]M˜ik l•:AšO0Ãf©¨rò”ÕU N9„”Ü ðRÐLK´žÓd ÁlÀFxeuPǬOå­P1 üBI è `Z£ Ò­¶ÈxM"nƒˆÃTñ»é=DxKɽqîûÝqT\Õ%žæš矟D óuLŽð>¶gíGw”fF[éÍI‰"fÆ{§S}²%íò©}¢ÁVLEGÁmÖ ë`Ú¸µ6˜i f, Åm” W‰ž]Ü8D«Ô´aå$4îÓó°1· "·V5T §E0÷W€é»ñì΂‡î³ûeÝçe?­û>‹béaè¸ÒËfy€³‘¯­®›ÑÜ@›EŸE|UszGÊÅ}€yÎìL´_”ám•ág—Œ¶H ²ö2cøZG¸¬Jô4ïçø;à‰w3N$Y¦ÁŠ«ùì/öæà=Kþ(3È“~3€7ÀrrSÁnUq:a†’è"} ¾±K޵Á¼ÿ>kÑ zÛüòÝÉðuDü@ƾ!xÖ©àÝ€a,Ùƒ­gËò#ô—ýÕxå›–só3‚:™?"’[*I/01·UÂk‘ €5ʯJåâ: · U@jUam@9°—¨h¥AÕu1«AnbªõP¡Bìäž‘¶µU|rù‡µ­åûo7¿ÅüâÊɲK'Öœÿ¶dS¾Ï+@;£À#êÀ“nŸj—l›ŠÀ“äg‘ècïm–â”æœæ À&/ÁÐŽŸË Ч‘§íRoGÝH_Ëh?Ëho‹0‚q¨›q˜»I²?nC OA¬OŒeÑxC.J< áiùuÀ!gx ü¼<àùúâb’ÌG°G½…xe®FÕ#ÁKðæ˜÷¡¶$¿ûW)§N08¡fû@‘ð´õj€Ÿ¶ºï>Ý•MÄ-§úX{™f$xò)ÍŠ)NN‹ Š t‹§yy;›¥Fûo*JÊaPÖäĤ†ÆRñÀÖäD}sxcýÕ/a’Ô캞)%¯:—Ä]p¬_‡NfY'‚ô"Šnp‡ƒÛÜíPŬzTÄ*°7e¦ëؤ‹±N‰çÙŒ€Õyþ?À g ày‰eéXj´ÆÔC½ ¨äŽ]p/ë€y¨[Ôª¶Éù-ÜgÕh÷°Úê•wvÖý´oSÆšìpOû•ÆKßœ³muÒ¦ÂÄâ´ðÈçx* Nv9ÐÝÒÞhá©ã›ê¯zPCÿ²ÛPåLØÁ° &9ƒÑ‘›Ô²"&Àðc”œZ9Ÿ[Ø&›_ ðT>yØ}çÑ 4x°öC¬ú_ŽlJñºr¤øÒ¾Ü£ù!âÞËÉ~>– ).7_ÇU$«e¾6Ëì—üm3Ãû`.ùÛu1_•†m‰s9½#-Êaq”ƒ. ]‘¬2ÈNq$ó0wÃô`ÇT»87cº‡i*Ñ2ÙË2ÁÝ$ §îº*ŠhŠ7Ÿ{dG:X™JÁhG¤=2V½x8JN³’Ó¢â¶CãtÊ9]ˆŒçŸÜ¬Þ²š®3ïüa–ÙŠ÷¨ÞVw:.âuùC<V·œÛ öùý¶†K£q6óW.ù³ƒåüÂ,jSõwˆì>»Ÿ km»*i§\Äó«…Ü ™¨¶´†¢Áõpî†ÒmírÀÔ²ÎG7OÞÃèf‚iå&§¯\ÉmzrýX/?î>1pë箚OÁNÃÅ=uG¸/ôß8ud{LùO^•‡uòÀ0%Âo½Ûr"6Ð41@ŽY&Ù.7vâ½L1àɉÄgGºgDº1h8«…x½’i ‡QL }J˜k2Í9=Ä-#Ô=Á×!’`™è㘇€Ë '$Sœâ‚l $¬w‰†´&T7Ö„`ѬLœ žÁxÆöóûiÏMÎÛF#­¯6,X-#ݪh'‹¦qMK؆*D(ÁʯÁß ²V,ˆ H ªÖآീµ ¸ù¨•¼.fÅG÷f3Â]½p«Ü-­WÍ·X9'-& –â„£€%„Á;Åë ÖæÆæ'‡ÒÃáþ6ksiß.~Ô{ôpŸÖƒå’Šß =¨Ò «‰õ¸‚õ¸¬›Ð¢³vT«§Ó>…[y¢o¶¶_Asž17TËŸ"¥™#^ðLßìršö—ÿËÀ3ã**-ÀóÒʱ_ð`Zßãý<¨xãÔözÏ ¤“½–À3ä/ÕÀ3ÓÖ4¡.ô“JÉXW%C—Ń—DÏ.£¨Ã„+}Y· ¿’ßWƒJ‚vÑPZl[;©„]Üþºkög0H;Ö&­Ë¡‡x”fF$…“h„Ÿm;¶gMQjتß2Xô6d—ðÕ ·[¯Þk±Q{øQw°¬‚‹tum©ZóGÖ·âaÿe„KrÕÎz²Ár®âÕÁŒŽºZf)sÑP£°–õôz9ÔÀ£@Õxà%hV Õ~¿7eGºïÉ­I;â ‡s(œö_¬æ›Îýo¼ÑÜ0ö:à tXá¬ûÖþ\Úñ5Q§w¤œÜ·;Ù³êhi†·IЧÃÓà Ã×:ÖË Mj }Þ0+Ð1ÍÇ&Çß¡8Ô½€êÊð¶Jò³É†”{×'"â›JN›ŠÛ þvX\ÆoRpÀºÕ‚ææi– 7Šêùýõ`—r:abïúÕ3òRü ŽK —ý=2ؾ,?¢µú¤Š‡ßß}>Ê>þ³6¿‡=ÐøÅ‘o‚¾™Á†+Þq±×ÉJ!?¼]­ÝVJnr†DüF•¼M"ªr+¥àúJ;Þ<»A̪“QCùÕ9ÞçN®ÏIÆû¸-´1þ[\¨ù_ïÙ5tÌ)Í€yv®¡í?8º‹.¨º×þÝ­æ¯v”RNÉN‰°:¶;ñU¸èàâb:<]ÕŸ“Ý—G’VѽMÓ­3)öd»œ0—¢BaœgN”[f”{I²?À’ã²`g³9Ö«Þ§zšæÄyeFzÐÒhøÜRV¨g6ÀðuŒ÷±”LvÀ“O~²°Q4\)åÔìQËˈ;\il²ë°rSÐ#h¯»üUZ¬·“ùÂÈ \§E4Ÿ£]ô`z1:Ø-ÄÇ‘æíPœýùþ²²ÜÈ=RâiNqTGŠ—‰§ÃÒ3ßníjø¡ÿÎ5U—ßB¦Ê;öe7eœfÀíbV¦ÖËL­ôŽx‰¦=7¢)LµWÄÿž x^šÂðo <ÚLÞ4δqŽÆ^i®ŽÖ§øÅ!ó·þÔèêZuÔ°¬ž±ï¼DàQs”ëbÓŒ¨nÈÛðÚA'œ E]ˆ°_‡½8›™UÇO~¹îèþ¼­kc£ñzKÿân£8'?‰¶±€^’°± >žêîç`¥ÿÁ®u)¬‡röuè¸?â>mTð`"„”Ó€ZÕÕ†jk`× ŽJaµZ1XZ/軄Ítx`ZN Ü«3-À£Ô ÷CÇÞzòàÕ©YŠZ¡`‹¸ýÇ '6'n‰ÆL¨=¾Ñ×|N„»!ÅÅ€`¹„d·ÒyÕ2Nßæã?,;˜Kþ¦,úÚ¡Ü7ÇwžÚµ& —N²L"ZĸÒ½-|,ü¬ÓƒCWd9¥¬ÒIÖ9AN™þöqSØo"Ô•d¿t}n(ÂíAø= v+ÂkÅ€GÝX >ùÈXµ’¡ZÑ@µ”Ý$á0å|´¥5rO)èþáøz7»Ef+ßÂY.€e¿©”Îú3RÖM%ï.‚ô4b¹й=þáëíž®Æ+?´4šoe2o϶¬Á§ðŸ”Ò. äÅL¥¤AzYO®B`ZE7Ø)É&¤Þø9%Ö>¸4†fêOXœAÇ-þhVa†çåS›Î~³zKQð÷‡³jÏïޘ﷩ jNñ\¶>×çÀæÀ<¯xàmÀ©‘UɇÀõmé®ùOB Y&Í¡ µ0Ÿî€§ Ê='ŸŠK¤8ä'ð¸[/´]õžùŠ·H¸á>VI!Îî+€1 c'ƒì‘EõŒ&Z§‡¸%øÛ‡{›eÇyàQòÛÀÕÑ <mб@ÞÂ6æ@àA½=¯xh–òÀS7µñÇàkòÌGBZ°.ó £h„:ŽjÀŒ/f—ò_CEסâ:@ 𼳟^ð`[®‰(î ÒۢᶻÝçÛj¿?´3kóê8À?áö©´„PøBR¤·ÎèhGó(J £JTcFœOgªóVR„{,Å)'Ñçèž¼Ÿ¿Þ̬øòñ+¼¾&T,úôóÈ»eDõ‚ÁjwToÜÿ`8“ª­i Ù0¡ÞgZà©F5Ð!KSÙŠíO´Ïï x ÆÔ¿°½2ày¤y!øyEÀ3%çü æÑ|ȃ°*?¸ZÁ)—³¯ƒN$n¨¿FÔW(ÁXq‘ÜB÷ún\®¿xøÇã¾ø¤`ïæ” E‘ ^Ç¥$7}wGÝ0_û]ei»×¥‡úØyã R¢ˆ[Jân˺ÛqþáõKP$,¸w圎þ;WÀ”Ê}´@?³ªAzÀHÖ€Üg—°q[ί–p*С² #`ÜÐxx ½ Àw ðŒkÏærÀ“/T== œ*ÂÓˆp`ìCÖuò“ô­)¤/K£Ê¨ö_„V|¶¿ò­PW}7ãyNshK'½Ù¾6ËŒ?üù¡bñ¥¡v¿ìLÀszGZ¨õü8gý ?» û´ ‡xo‹(¢)žxO3ð–Óèvˆæ±ž¦1$ §•Ù±$ÑS°–¼­nQqš¬èn7«E F ¡a]Âå`-Üʨ#¡¬»½þÛ¯¬&“LôÿÕpé›^¸U‡v ÞoAT}ÏÖ`¹âR€Ò§Oï7eG꾿àÃ?ê/;6ÜõôÉýª Þ ·âvØë¹ £ |&€°8ÇÇmáá= @>¥¹>É1v8«wָؼwh=+·©(èëƒÙ~XŸ‹;÷}Ù®²·¿ý4÷ô׫vÿ¼oSÔ+éÀ5ÌÃ^ÖžÛÌï"| r¢ £Ý6&û­gøä„á"= Âÿ¤ØRÿy‰tbDRcü RCC‰©QÞ¶óÁȳýl÷nÈÜ\ŸC*É fÄ‘ŠÒ‚J³BJ³¨Ç?):õåzDÞ ˜bø|ð-²@­üg5J>Ú—×"ç6;à´‹.sl›æÄNãÞ™ ðÔj‹zü;¤õŠgêã±ÌœZ`°­ fÓ3Ï+ª®šZìhü_÷Bçí·L‡g*Ìh$kÄ!£Žs½ à…¥i %«áu#üu繿àçãw”ÆÅSá°´<€`D&™QH–ÆËߦøZf†”¤‡•f…‘‰æ+üŒ6wÚÏI‡;Õ}Tµ¬æèr›áhƒ.¯PÑŒ¬³$à¬å\üŽô·•p*CWÀP °›‚Zѳ+OÎxÏ”ÀÓ ã× >ù.¢…õhns=<’g€ñºT5ŸmŒ:˜O=˜œK2>»=íÚáRÛÿD4ëj8‡d«C'ã f‹H–‹-æý÷ñu {ÒýÒ¼ôOlˆ9¿'íÛuqþ†ïÅ8­Ì rJóµeøZÓ}­â|,3ÈNI>ÖÉ$«|ŠËºH¯u1Þâ|Ëâ}‹¢<Ó¨Îd¼ql€ãÐm°æ½«dµ"Üt^h€Ída‹íÚQ¦¢jœòf%ì\S…¬j”O`*Ž`ù ÷Ò¡9$g}pòW,ø›·‹á±¥òX &b1T5²Cö!²Ç-õ?'Æ<ú`ö»³,?ŒpÞ¶‘~áÌž»½gÏŸÚÌzrõÊ™­ßžŸ“Œÿé›Õ…žiñŽNmüd{Ü·Gs9Q¶kcÄÝî“g¾_{ÿú©¾›çn6×Rq° =Ä*á^qfçጠùA›‹(Ç÷gìÛóJ€Gòì*¸1Ô½´d=7¾¦y®À“IsXO,ŽõH#ÛRœ–“uBÜõc|Ì’Cp¡^&‰!¸¼žÑ_(VòoîX›h½âí(ËH¢Ezˆ[^¤w”§M´‡µ¿ÕŠh¢uR00À¤0’éýîsˆ°Ήڀ«FŸ`âFDÙ¦ž‘pEË+KZÉW•Yã:þÔŒ±*AI.¸E˱&Ëa£ŸƈÀ*\&Àxxcs Røe+v3c³šÊbðà ‡*PÍ®:”íÑfÓh_ γkЃ& ¡h•ünåùÃ?³ýüû7%D“]ˆNñT‚ä0_§:¹0%ÔÓ~%#Ì+æžJ«­2®(œ•àîo°gûÚ¸†g~*ñ“mô¦Š#œ§U°yL%º#éå'ìÀd"Àr ­vo˜,ó¨=yÆÀóœ”×1Áõ›ÿNZ~¥À#Ö(DÕÀ”âš±äƒíO¶×xF[c4N©PôzÆ!ê\e xf˜´Œ S“ßwN2tNµX‰–μrVu_ï…š_«ýR“¥ ÷µˆðµ&{ëÎû£ÅÊ‚Ù6%ÚksIb§=’X’yxg~gÝìGM÷:/@72ò@ÉëB$pjàöWð*`› V9ôå"œþ ðT«…Î`~#ñ$€dÄL,×ìƒU!¶$„Æ­† y,c‰…^#Âûõ°%:«Øá j’òªð@ˆ´D« N‹ÒŽgݧvæìËú²4êPZÀåÝÙ›â<]W¼ïm‘è€[åe»—Å"ƒÎÀsj{JŽ¿ÉÅ´¦¯Ëj¿(Ë ´ŠrÐÍtŒÅ…:ë%øX2írB\3ƒâÝM²Š©øªkq¨ûšXï’/<4³`¼éÃŽK°»áw ‚V4˜…Š´@ŽL[Ž™’yü rA-wà*œ$ír~ËœVT“äñÐÆ/”„úÙš¯ü§þÒøyœøz£„߃¨X]œ6)R¿„w³òòñ¸w £ÙÎv‹q¶‹ŒV¾mªÿ–¯ÇÒø0 À6½­ßÚÙ\J„3üøÊú¢@ð~I6éôwkÎÿ¸AÆm¬¼°ëø¡Œ¦ŠÃ›Jh»6D¯/$g$¸~}8ïÚ/»lOŒ¦˜'FØeÒñ)1N¿1ðÔ`6ÑÃ3ÜÒð¼´¶\=L²[lC4L ´N´C4¦XÅú˜E{›–e©Cç®ó€]>¹Q=Þ·!ÉÕln‹ž›Ùª«a”—u„§uV¨Wˆ«Y¼—mb€C~ŒwÍÉ×iyOý÷ˆ¤ÜÕÈ xd«lÒà÷Æ na º?DXíïax3i+„2}2Vµ”ƒ&Þˆà}[ð—ŒÝVÁgÒVËñh/ÂJðP X[7Ò‡NÛVÓ²¶áŶ /m+DGó7Âk²«¤ Í(C¸àøP‹Ž5SÿÑÿBHK‹sFX3xœ«£JÈM ðpªܪñÛj0¡ÏtJ‹+žÞø±¹bÿ¹ïJ¾9”S–MÍŒñ*JòsÖ3\ò?Ö«Þ7^ö“9.– ÃlÒâ¼òSƒöoÍX“«§°§ê1øOk}5Ò¡9«IÅã2â¡kà¥ì«¼þsˆüÕè¹C%ÌfDk=˜’¡JX=v(Ppj!‰Ð_PE‡* ç ¾Ô]S­€…ê x¸€sЈ <üÙàABhÑ™å]Šêû_îÈò]ã\~ ¯ç»­§7%¦ôÍ?˜eôþ,óùÒÿà?ÍþÍËb¡Å‚7,çý×ÉíißnÈ6]áxnOÆ•ƒi$“GÝÒHbª¿}n%Íy%·‚îk˜'/_Nȧ¸0ˆt‚YŠŸuJ€] É<ÔÓÌÇQ÷6ó4”K`°–µ¢ÊÛh¤ëMÏ-W²¯Ê†.‰1¡¶Y«`°’õä*T¾UÝ„µ·‚.¨Ø#¾…Èî_oúiMvhˆŸEˆ¿¹ÞÒ?쩸x‘ÝETUâ;2þæÚBV×/'÷øÝ—y{ÜWÅ…;yé9[½_œé“•ˆ?¶?½±üS9¯éÚÙí9Éx˜Û ï:º?%’ltxwòö²ðmkÊ3½ýÜ—xØÎ>y¼d׆¨Ý£K²|N}Uzt_ÚÎõQG÷¥‡k4áGH´'yNM Ze¬0¥Á*µ´²Æx5€yà¿rQÿž £õêQ‡¥i!ÎàrFºƒ+’FvÌ‹p÷µ s7Ì Ã'ØdGºGMìõßµÓ{§áÂgˆòÑç»rƒ]õ}–àôè\¤-=Ø9FHôwfø:&ù;¬ŽóË u¥¸¯ª9{Q܆qv V¯IS‹q êÆšHÀ9áô_ä \Æ´û°³4¶<|ð¨‡á¤-Vä>y‹¥FOÞŠ°¤âzõ‘BTQܨþW^-–œƒe»Á‡…[£äÕ`ƒT ãU| <ÛcÊ!Çʈ5ª+Ó¹uJN­Z¢GËçÃÕúÀ˜¶àцïˆ'®=5ÅS0FÒ*å@ª·(–ø¥%÷É#öã¶¶úÓ§¾Ü™•àˆ·_î‹7tµ[f©ÿ#š¸©8žI°ÇY,Æ[éÆâ(»Ê2¿?²u}^üú¼X*É2–âT–zåç}·ÚÎÔ^:|¯ë ,u—ß‚)‹P5«SÆ’Y`•»ŒÁlg¦ZÆPÖ0Òœ÷*ØÂög8cL kDÖ$å–ChCS+”¢rØêE1Þ$iP÷ëAµ¬GäÐ[ÆõvI®±&õVÝ«b,E`bøuS›¦IÐõJ‰ëÕrúê.Bõ#5aÛ0c]——^E5ž|ÔOܤçÞ¨ØS€ÞpÊãÕN÷ µî˜1[—ùÐPt+}9û `%÷ØWωÀ +à*"­Pñ/)¸I9"+©K ÎeDPÿ»¸F-ªƒµõK?N¯ï"¸9Á:°<¹!ª¾¥‡ùmÃåOO}¹þÀæ”ô8™`@u¶5šc©?ÛÁt¾‡½N Á´8“º6/ÜÁ죤hüî-ÉÕW<{TuaMz`¨NÁ©— W‰Ê…ýWEÏ®H‡ÊÑÁžgðí²aØ0'¿J:|EΪÄlàUFïCtt¿¼\k»JŽ ªur ,BG—ùàŸ`ƒcð.š5Á«“ W ýÀØÞϳ¤‘×wÀ©‘T¨Ðõd q;¢è­=±qs~G<þé¥C=ßogsIÔÝÎs°Ó„äðäh÷¼ÿÔb,Í1ŠæàeäKXE± &Y¼³¡8ìáóáÁ&w1ÎžÜøùÁÌËg¶ÿEÑÚ‚ÀM¥´¢,Ÿ`ï•ÑszˆÕÇò7]<¹¡üÌ–]ëBüÝpŸ\yvÿ‚~ ¿(˜¤¯®úAq± Xé˜1ðh}jíTŒ´“‘\VÊ(ð4œ?èe³8™âTíµ&Þ'5Оák]G*Š!2íR)N!øUÉÁ‰Av1>~NËãm’hNžÖ év$sŠ›Q„•“Þÿgï= ›ºò…ß¼ï}÷Ý™{§efÒ¡ƒ±qïUro²-KV³dɶÜ{ï`À€Ó{I#! „‚÷Þ+5„ 4wõjÙçý÷9²0Åd˜•Üy÷¾o­½ÎÚ>:’UÎþïß¿‡’W²½mbƒÝJcà "ÖÄÒ2ØäP×%U_o‚ÝEzL´a¿]ÛtöÀ½kµ˜âG »«“\˜” rØÔUÉPTL ÞmÎ0ͨ‡ b¢ ÊJ#Š:bº¾›@{Óè½J" ûegö–:k´Î<º 8IPüè—¿Ýîg÷a"Ã5/™w¢|-ÂÉ+RÂ\²¹ž1ÁvË8ªv2#¼Òù^Ü Ë·9~fQA¶Ö,osn M¨‡)•dÂô´Ê„ ‚ÓÃÜs8>ð"vóN~´ ÓÝD1ÿðÀÀtýÛ»¶/F= Oïš$Þ0N;íú8(E1p£P#A;p~RÖ ¥f¨:‰†6OŽÙ¶Cûõ®™ï†ñ$·?P¸c}\i3%Ê3Øs9è5æ‹ÿ`µìÏ¡öëòø«29¥ÙÜUÙlŠÇò¾gWãgбÔ¨[h¥«/iðÙòñ&ùxƒl¬^!j@¹T’& €@0þ3 ‚ †<†ÿ€ CÄ‚?Èž3ÑL4ݘ xT#õ†¹ášÏ´¬Mrÿú]Ý(GÚƒ·xîFþݵÓÛŽ­á”Úy0ÿÜæ¸Se‘5Û’®ŸÞ:ÖöÅg÷^=¨ýxEßéÝ’«ç¯×è=¾¡é£Â¯7D22C¬OmÍN§:Û½÷;ºÃÒHo‹P»…t‡ÅËœþ‘j7ßÏìM^Æ÷6y-Ðò-tǣ̢(¦žëªÚ ¢R3ÖŒiûTã ?6kÅ5˜¬“4êÆjt#¸…g¼“t(6tÕì¹õÔ”¨ûjçgëóBÂí3…äòVý·«ÒB!%Ê;’åÄ£Ûq¨Ö|†C4×-;%DÈ!óhŽîË"ÂìíÍþêéüvb”k^šF‚§ù}Vˆqb”Kv²/ÕoIX ñÐO5¶Ä$y]¯œw©ÇZÇîœÿ|š§ý_Ìÿ3šcÅ 5 µ vmB²ÑºŸ¯Ÿxpë[ Ÿ—¢ñÊ3£åÀ,ƒðåÊ£eÞÖïÇÓSÃÜ2Yä$ªc´ŸeN uT 50Ë}EÍ1žî”ÂuÏ‹ Œ¢Ú%²Ýxˆyl…t—ÈP—p[ªëÊl%-Ì3)Ä%›í/ð #/ÿê@†ÝVOt¿,ðÀ×´ˆi{ày)àyÊ#/Çݪžð¢ÃN‘Ñ€l,šËò±NÅ8J`” ·}´7£ò›Øäâ!ØÁQ³-<Èÿêº%»×Üßr,–C2]ø‰žyITf€E~rè†BÌC½Mi>f1Ý™M±)Jc–fGT¬IÚP³½,µ4—`Iõ1YË^“¾©4²éüþû7ªÅP•T&¨¥»ö j(?uïÏ‚êü Ô3<4€M%jRŒ×ËÇêT¢ú¶i'€gRÖª‘4j¥Møùö5öš 9O£NûcÔQÏ Ëã.ÏOõE?:qÎ!bÔÛ‰|º—žÎ§ºhý#­L~eËÏ“a<¿° f›.q½ü¦À3gàn½=ŠØÑð¤ßñ2ïg.Ú!:úZQ3’¨D©ñ¼j¿z¬];Þi~À¦´·ä{†®7tVüÕÁÕ·feÇÕP½Œ Sé¡Þ&$ëwH‹Ý¬ß.L£íÝœÇ%1ÌJ2G÷µUÒŠ.\ëûæþU¸:s$<î=GA†°IGj´ÒX°°„ecçÕ5(ÉHÜDÐŽaꨆ«ásdáE GI3<„„ÚA©‰æäþyi@NOÑ;¹æ²ðÈÛðHPáA¨,éà™DmÄ»±éë×ë6.¨,à©,©Þš8ÚüiÇÇ«÷e…Q-ß -ùƒÓ‚ßÁ ;}°+'¬ý˲¾›¾­HÚ—ÍÚ”t²"'%È!î¶*:d}bXƒœìh'ð6Ëð‰£X§Ò`$Û&QmShö ÁÖBŠ)“´Àì½WO–cš‹wÏÁûTÃÞ¤“6À·7%Ap¨¸Vó°ÝQÚÁ›¢8ËŠSý¾Å.šæà G‡ºRâ©älŽ_n¸oqdІTÏÏõþI1ÚñÒÀ£î=ª»–(9hèlø›Ï\ 2ð<Û1ð)KûSàôkâ9­à ™ x”¢6lò‚V†šäbº‹êÐû5ÆÕ]„óЉVÉH#ŒiUß´øçæðíš»?œÁ´?ܺxzïæÄ5¹¬xž[’À“B^Æð·\žÌ °Ð]™þv\ª‹·«ŸéÎ¥»z®ä…¹î®ÈÜ¿#7IèŸåË ±Ž`:íÙšÑÝrìú¥s·.Ÿ?@î(ʎȬŸþ›¼Œ—úéÇ«v?nØ1S0ïÒÕB ÂD8˜ž³3†f£á,ÓÑh"&³Ú²?yÄ˺꣯åxÞœŒð®v>Q/ð 1g™‚ßxæLc^Äò/Ïl÷ëÿ7ß¶ô‹nfAœ ¿ð¼ÔøÇ€å釨²>3ä-øhÂGÃ㡨'ÐŽVQœ¼¸ÁS+GvÎI²:NÊÛ5â6ùx“t¸~âQÍøýš±{Õ¨Év¯š~S]ÖŠ0íMÙP÷Э¦;—«{Ž}´³ˆOsIâû­ÉŽ\É燸Àp4~›ío—ág³ôo®fo§Òsâ?Ù™[u¼âËE­çöNK.`Ó71Z¶“¢.|tÈ5 NÙê~ÔŽ\Ý­ž¨SŒžŸ”ÔÃû׊k4â*S$iŒy–yU¶*ü!ðÀâÑyðÀæš±F`˜¸´^xT]²‡UDBTƒWÜ¥w!¹§¹JYÓÑÒ¯ÖE+äð\þªl¸éÈöx?Ïÿ»7^q_üºã¢@ë÷íçÿ‡ùÛ¯¬üû+Ÿoˆ=³;{Oã³µ1<;29©¡Î/óp’1Ï}e„‡i Í9ƒIŠòµH¢:¦3\’C2˜Î«„~ë“B`¬ŠñÉ3Ãßé¯Ý‡a×T#ð>5ÒGUZqòmÉ['ÇU«1E÷äP}×¹­<éQ.«3sâÜãØÖi‘Îy ÞqlÛC[ë¿­Ø\ÄeÚ3}>Ë‹’)?YBŸ4G4Ý&IÃ.A@Žç“SV¨%0OJ´oZL ‹ÕÛK׆³BL(^ üÝ?ò^@ñšÏ2âÒÍ"]>ÙŸupGšÅÒßù9¿ûÑödL:Í´0[ôJoãåx;Ü„w$ÄFØg…ês%´½ Æ¥#µx&û~k—Ö´¸n9ÍHƒv´S]ùd[ºÍiá™lÄÔ鬀ï›@uˆ°Š ¶‹ÄIa.±¡4·¥Lo_»Hfoöð¬¨nËž¦a^¦l›D¶ËË:å›D%e±¼2ÅÂ@xʆ<¦¾¦ïziàÑôðŒÞ«$z9Ú(ÿjÀ3WøÓKv‹ëcÀ¿xžïÒRv*&š1Ý RÔ2ö  S÷NÂ;Wõˆ‡ëÔð†Õ½(É]Ý sÙX#\ ƒ­Aø p¢Y¿±Û—»ŽyxuEiìöu 1f€UƒâH¶omòFjlpt¸§/Ɉd¿€hà q(ÊânÛ¶{þ¦5‰9)ŒÔX*Œä˜€ò’ȃÛRk¾Ûvþ›-­Õ{ÅAT^UQßáK‰j7ÚKUûÄ_;—Í|jÀžYPÑöf”mOrÎcïÕ çh‡-zÂyvÌXŠôIg3å_x~c—Ö/Öí™M;h½øŸ@÷Ûy~kà™³tÌŒÿÂðÞ ¸—Zï/<<'ð£ã飬S?Çãc1Q †çÌ"Q¬‡œÙ¨ó$íÌ ¢A-­G¨£h¡‘µ¨¥ÍxV2ZÅ“rdPŒ·+':Pm.ÕEL{Õ·™¾ƒ©~ÔŠ.Ý»VÛVõÑù;Žî[zJ\¸Wb„o|¸¯ÝŠ·~¶q?O»¥ñ,ßÂDË×>=2hçÚ”,apyATûù#Ã7QÏJì6ª¥¬¼„i®Ê‡šàL *†êUC —<× À'ztãøäx­jôœzôܤ¨fJ\£UiÆ*µãçÑf$i6ÐáÏBq;Òx 8¤«C®q G‚UÜ#èå×piÁz× ZnàÑà…U'¥=˜¨ç›mÉGò'Vó®Ü¥»p»[ï±ÔcÁïB­ß '/§:-v7}ÓËâ²É߬Þy¥áhYóçëóÏìÎ+á£n1~V·aŽK¸d ŸTº ®»qL€e*݉庈ë¾,‹íšË%'†Ø$„X¦†Ù%Ñ­y~ËOìË’Þ©E1Eònxÿ’‡UŠ‘:"iE1T‹8Y{q°nw,ÝtG)·4“²s]Dqª_A’÷ÖÕœŠUáù‰>k2©g••f„Ò<—p)¦.møtí"^í‘ÒíÌb:Ý-‹IN¡:%‡8Â(mL ËsÉf‘`äpÜŠ#½‹ ‡,–S× €§4ÎwM¢o*ÇîãŠø¡O«†Q±#LÝÜ‘ª>˜#³´ëþ•“‡Ê£"—nÈ ÍŽ%¯ÍÎKð\•P’æŸ)$ðdD‘·‡Ã¶›áV–À#Ýki*Ÿ´>—Äuã‡XeÇøÜ’zd[Æ—Šê¾ÝH^”AâÒ¬vLû®K×9Ðk9?Ì1Ô× €'<Ô&)ÊãÈžôŠ2+ĸ #0^à’ýkF0,™A+£YöÜ`³h†-‡²2&Ü~ûú¨²BÍ‘ÍÊß%ƸE†Û1BL‰º¬ |ˆç…•iÿ‰´ô9€GÖwòQ-À9ÏÞ²Ø@Ç…ùÂÀaPN¸çÚxꚸ$šSÓ-/Ò0ïDXñ,inKßý_ÎÆ;°1åðætÙ(•ë-qtYžÄñRIi,ßt†Wy gUtHy:‹íe’%ôÒLô+Ç:ÿià!¾ŸÿzàyÂKõày–jˆ”Øû¼‡~mà!Zc<îáŽLjæÄE´èÂë±·ÉFóhúQë %º `F6Z§šÀ™Gܘ$Ed²ÑZµ¸I2\¬´È)6t¨Æ»ä#íQ/"õ5P!QRÅUÅØ@sí¡»×ëvlJòHaA–0ò3˜1< |—„HÿH¶;`µÉ[þîfžfþ®Ë#Ã\nÍI‹Ðw®O)Íæ¤„• 7•Äl^[Qh[víw{^oÄ´¨æ<3J~W_ÁT—‰~%ª‰" ˆ°ÿÌ.~8—K ‘žy äÓ<<Ï‹áÑôèSà‰3ø«=îÿõÏo–þ‹ÀóTMÈ ¾ËèïÀ_Œù>¢__„}U¿!þË€ÞjósŽbT>E”à2™8Êž2ãl3cðyìíB*t¨¾\£l¢])éRI»UÒ^µ¬O§¼ˆ¢qÂÑÜ”Žö_»Pyæ›í;6¥es–¾÷{£yØ^–¾c}FyIÂÚ¼Hó%¯z9-<²« Ð݈dµ¡8’j·cCRÕ·»6 ¶®ŽÑM\Â4?¡tfõ h@3Ö«í’?jÁí}A'îTŽ4JVÝ9‹ä›¸mr §m$ÒX=º™AcztV~ÿû©ñjL\§>«ªÄÄ8ÿÈá«à¶ÂчŒ<Ô™åÛ2lmày\–ÿyÀC8°œëU#õC¼þ± eT¯X5ˆIúŽ•ÇîË 9¹&r´þ°¢ûØ…¯7ú,ùC„» Õv—Ù»L/‹7c8†º.5ïµ~½µëäöƒÅçö—,Š^Ge»,ç‘…¾–‰AöIÁëhû £6§2våq…~¦püª"­<%¤ïþIY܉í™’({ ÙßîÍŠ§ší(fcòALsQz¿arÞ|&ë‘Ü«$ÃĽ§>-(Œ÷W3%Ì¡xuî.ù£oIÛÿbàyÂZŽƒŠá;7|Õ3À6§«‹Xzè Üò¤“ë©×7–\3_«ùç!dS_»E OS£×^©ù¤ìüáUßíÌ:Q‘ ÀS‘ÎÙ¹&ŠòåÆÌòÄЂp$Š ×uiÏ«„ïµ7·;‡}¨Xðùú„‰‰³\¶Ój¡wE&uO1ku¼÷ÖÆ7s^=MßÀƒv]1Ü¢ƒrtéBËÇÙ±äâdÿd®ýºœ˜§ð“xöG¶%TŸXŸŸèS’¸¥8|׺¨(š5@N˜÷²Ü8ßUi!×ãX©|RqJÐM‰Ùq>¼óøp'Í*3Î'‚f%`ÚÆòœS„<º•7i2ò¸/rÈ!ÞÆ¾n #Ý÷oO:ÿÝæÊo6¬) ³©&žú¸½ ´Ã ³ ù…®¤z¯ˆ w ö\¾±8bU°lÃ*Nv²'Ì,$` “fÊc[•±‰ˆÜ‡?}GÏl'¡¶?¡‘= <3¿þ“1$¿ <Їuº±îD¶“óŠ¿GRl’Bã`?¢Ø¦Ò]ÒY¤”0—¤0—T)Wà›á- ± q^d³ø©<Lu«áä.çååøšsý­i&Â0R¸¿-ÅÑ(ååk—ê–J#ÅÛER¬#‚ÍGï4¢"bTôû©–µ¸qォé%Ê ߃|¬¾ñç€|ˆMœHA""½Ÿ<øZx"ˆ§X8QÀC0W Õ"]€0u¾ ð<">§˜˜ÄZ#Ô ´ Ÿ,ÚóÞÂ#ï$d‘ª|ørÀƒ"'{pÎ!ò5f&øIýŸ¨3ob¸ €gJÒE„ÊÌpŽx@åïQŒ5©áw$ ¼O´í½4ÒΑ5òñ­~»6-p”î…¨gänã­*e#={N~º¿pßö¬õE‚´»œ {Õ‰`C!‡zYFÒÉÁîfn– ü]L¨žVÎfó,—¼ž!¤•fEm_›¶£,ucQôæUñks#ŠÓYÛÖÆ;¸úÔå•û/v|u}àÔÈíL{!vÃn GÁ?S—ñDmÊñVâm —õ ’ê.l² M7:jg@¿h º„èÓÏuðAzˆê@Dr:6Õ_ÜûDM?C}›§šY¨õý×ôx3 xþ™þÿÿžÇ=»ŸJTÿφßÇ;o J/‚-všˆŠ¸°é«†Ïëψà Ï´¥ÃÙðY•¸ÓÎ$úM£ÔDæêÐ#£ $*F;Ñͬ¸‚7¹¾;­º>t·µ³éè™o¶}ùɺ¬ä•KÿàdýnT8ÙËu1Õϲ¢,eç¦Ì­ëSï(t³üP@wM ÷¶7yékñÕá5Z¾P#—4j´=ÖŽªîhÐZÐL4ÁÀ·½„RK9\ ?Šb¨•Ð5kÆ€Z''šÔ£õèÇ·i‡ë§@ºÂLJ#Î<š¡ZÀžÉ‘Zdà‚Û ¾Š±úéñCÐáÕÈAyèxT‘«®܃‚.FêõVKøó¾öÇÀƒä¼AÍ4Hþ—ÔÉk¼ë»i§¶$´,ü¬TÔúÉg…áž‹ÿÌ%›Ò]ŒÝLÞó³_äfâiõ!Éôm²ñkç¬9¶9åôîœÎ¯+*÷mˆI ´[Å÷‡¨³&:`] %ÒÃ(Þß<‡é²&Ú'—åœÃtZï_çWÈsà‰õ3*à;÷žÞzp ßÛìÏ,ÏE§ŽÝ»t |y0ï§ ß¯v£ÿĦB¶aU’°©€™—àyxkü7媈ƒÉ©ÏŠ×çÑ¡fë²iŸïÉ*L ,ÍæÙ^¹.;,1ܹ$5xu:5Kèòϊõ†áëô^F¬·íhìOžOñ\è±Ð‡ü!‡nAwà‡!ì òZâ³ÜÓùí@O«œçÁ°ˆâØ$D:'E»%Dº0(&¾nóÃüÌbØn¾Î yt;a¸s$Ë®8;„â³Hnä­ä0,`·eŠñ*©(ƒq l „šŒ[x€a × íij€ðËÀ£ XA¸©‡h¡®Å=æOX¶ ’!ÞÿSÏÿ„Ô}Yà™ÑïÇö¼¸t<áÀE‡^1ÄB¼ ðèépÔÇ?wš4=å“ý?Àó"ày¶ú^X&âÙñ²À#cȺà8-é0 ü$*¬/5#Â7wÔè$U ÐΠJ»l@­7@Â+;•¢&ÙX#è °ÛNÊú1ÕÅiåELwAŽææÐíæãŸ”íßš¹scrQ#–Kös[ìã²0/…^šËós[NµÍN öp˜çá0Tr»•¯;˜½¹¹4n[ilõ‰7{¿¿sñœz¸ÓÝÄ$ƒÈ¬¾ k–?ªA 7GªuÒ¢z¡N\?-©ÓŽÕèPNP Š=ÆiMöà,aÁ£tP®H„"‘o&Góh‡ëò3ÓãMÄyŒpÖO4ê3Æ µwˆ˜d¤CDòÌ3›ˆ‡@3%L4ÄoG”â1HÑÙ >!QóÐùI¸´'לÀ£èý\9x”jq Z÷žÞq|}tÝŽôÖ½ÙWo(ã¹’ý1)Ô5ÂÏÖÝâC«E.+èf!®Kmü®íDEÕ¡âã›Sj•Ô^½)!”böVÓ-9È6‚´<Ö×¼ œ\ÄóÈ 9$š'Q,R‚­2Ãìò8΀:%Ò¡g:ͪDàúÑ:Á–,j¤ÿò ÇwØÞK£B-¶óhÞK÷nNÝo=²3#™ï–ã‘ãY^È "½·±€~üpîÖÕ¦ßâL¡ÛÞ¼ï¢ä¸ "ØÀ&•OæIæ¹§,å'ø ìÓn)×Õ™!ksh±l»]å1kù)1d.Ý<:Ü&QHŠ w`YÅr=àîbYÐü}IïðxâN…™”5ô´8H¶€iê·îINˆÍÏ )Šë˜íæéún¬À9’gáÀ ·!$zšž¡;§Q ¶¶× ‘žîj7ð*ÿÓ¦?Yû\À£›h‚[ýÐP9¯æÇù»,)ˆ¡ü³Øî9ក=© W”¨E±áxš¤0ÝâCÙ^&T—ÅN ¶¯bšÛ5_osZöjD 5ÓËŒãoËt÷·Pœ“èžE‘!©4Ra”*‡ìdòçƒßaêËÀWÓãÍx$DΠ¥KÛ#u¨o‚¼ƒð ÀgÂÑHZîß<9б¾Ø\p3oaáA9G³GÓƒLC3®^½IÄ<ª¢Œƒ64}/òg½xf§\ÍN¼zAlî>&élûêËnÛÑ(i;¬Yô‚êžÖÂÓFTäÀ-<<=8çt+†A\wêà¦Ý“¢ŽYœó$ðà´©7M+A>4©Dúb€’FùHÒ锢ûg¥`Ž·oQŽÀ.Ð!}X¯èĦ¯aò ˜â²ÆLßA‘Éò«o4\ë;5Øúõ±ƒ«9AÖkrx@2|š 3À¦8S^›Å ±)v!ž¦vÆoØ®x=ÀÍÈßu¹—Ã¢ÌØ |šÃß² %lC¡ -: +޲umì¾-)Šx‡wd6Vî½ÐþÅøÏ º^ÆT?L£7€’OñÖ3—ÔÝòÑV€ÔÏÆ"ÒÁПjT·ônÔ¸‡à`í@Þ¿u€¸@98áÝ]c!Õë*ðüŠ9àϯ·ƒ;° Ì3Ó×ûWž9c~æUÞõc"ÜÄèOUPJÍVÀmÐÆ¬kˆU n<¨9zÜl*‘žpðâ ·pãä]­äâÅ®ß]_VÈf»{™˜-ùÓŠù¿'Ù~0ÿÍW|ç&S#Bíp<íçY¯xÕÝî½Ôß4¡ïÎò„ï¿,_“ÏÞ»9Y'¹„jŠN߯TWç(/c’^Íìmêázå0Š¢Á-ª‘³ÓRàœZÍØ¹)q L´ãçu"ÔÈ`r¢Åä€7禑yªÑ<ªáZ¤8ã,xê ÎÁ$mÀ6@>ð' aó?á$’yi«lHEŸmá!®!!$ ²PWá¤AÔ¶B„p³Lpðt”·EÄ<xäí<¨× ð(ÆÛU"Ô]By¿ñÜܺý9‡ ?+ýéûm«Y–¯¿äO#EPœøÁnt/Ë #·•oðœÞ_ðí¶´Ê]YUûòŽoL:˜±+“½#-p_bù.×uiÓ¥8Â3+Ìa­Ð€'?Üeu”gÃ>>À¸€ë¼%5h]¬'Ïêh×-!kü~+(Žï;,ÿCiQ$Í–KµZ—Ïå…Z§ðÝsüâXk2©I¾éQ.¥™”²\ªa±«Œ?ØüÑÑ=™Ñî±LÛ¤Àõ¹ÌŒ(]ë„óÙ±LûŒ(2<&a¾Ky!¦B÷â4JY!sM=†çéR˜´:VÌcÚDв©Ø4ÿ•T_#^˜El„#Õo1#È(!Ò9%†#œfÆ 6NŽvb; .t?«0kÕ:5Þ—jîIzI3e‡™s9Öál+}<3€Yxžiyó¸ÙßÀ£Wùñ“ã?ñ‹€Ý{ãè$&êKwñ´|7-Ü#)Ô9>È.ƒIÊæx$†:&Ñœ’®QÖáil2ê+áºÄÝâ­Mù˜ìǺ;ÜŒ_ ó0àáQl¹{ŽŸMÃ+&Èu•¶>‘åc]C)Iq^ùØk0íUíhócàÁKRèí-Ú>T#Kªt?±kÕð`Ø%˜_è:ˆa—‰Âð(  ߉|¬ž êSém8³"$‰Júõ‚‹¸Ç©ÿFÀƒ¿ þçÖôþs1<ê<†GÖ«m‚#:)é1®jBꪸKþ¨nJÔ9<½ÿð´MÉšA[¿û-¨· ÉjÆëPTƒ¦>5"Ü:Ý*úù¼ì!ˆÖfÉ=w­Ò‡Š±6¨Á°Å€Ì?l‘ ubÚ›ã?·^é>yþ›í§m*Édüíß_Y“¾67bcQtnbhNŽt_sWËw}œy;.ôqZâ°ò-²Í‡n˼G„:&ð¼’ø>¡Þ¦B žµ©$zçú¤[3nËüòpIkÕÁ{תeCÝêñ 4…¬@ʱNDAÚ«²á6­¸—¨aïøS^D…ƒ4WP°°ùÞ¹ 5²"‚#¢JœŽ`Øó¸}*Mý?Ö¥õ?xfÛpž°çÒÈ» F„@¨5^¦оwÜ*Èg4Ñ G˜T Gƒ:ƒ£z›ØII¿F|I1:8~¯óáͦ '¾<²&?•Æ´²5ymù¼·^ñ7Ëå¯Â8ÇÝþC€7.o¸,²_ù·…¯¿æoº«,®¬ ¼õÜ^Ù0 @¬‹¨ðŽ¢ÔLwUv¿Gþ3«)î?9‚’sQ€.¦Ç0X³¿Ÿ–Ôa’zÍØùà©Ò‰jôÀƒ[xãIÑŽû`ûàÑ¢¦~“ãD‚þ‚À*Šv¤©žÁa¦.&ž¨ÏÃ’èC— ;„?ËÀ?†¸eBè*êè½é=;QØø^x¦¤­wÏ àuf¬ ‰¾±6` »vµõãƒÅœckøÇ 9íûs¥G÷¦G–çp}#ýíCI& /+Š«Që²`§…›w?__¹+ãôÖ”3ÛÓ¾Û’rbcÒ±uñÇ7¥¯ŠðÊ uH °Ì Úç1]ã|MÒ©6«0J"È™4ëtªåúXŸM‰yl»µB÷uqî™ÔÝ…áå¡Y÷Ø0‡#Û²²c¸T›¶["ßcÇú¸â jZ”ûšìÐkùñ›L¡[v,™ê1ouF`gõÞ/ä®HJ‰p)H (J¦¤òI%ƒåaýF{ÍAØ# !K E/XyjP¨À¦cPÑô ¦ë=ª‚/áb÷! »HìæD¸ÀüðQͳg&}ƒÐIG7h°í Sç?aáùW¹´f]i„þ§€½UÄ9è½u£/AN|(„…xÎ#¼«N(k’ÐU§¥/¶ð4ãÀÓŒO;ªZ¯îT W#±¦h××Ó À½§EŠ!2ŒH;ˆFÆh2uAú¨Z#i&îíñçDk0í€ZÔ.nF¤øçæ”l›¼¦ï>õEÙÕžoÎìûâ`IiTžžGù¼X˜œȩ̂¹p‚ìÝmç3ü­mÞ´^öúÂ7þ·Åâ¿Ñ¼­Ø;Ý536.ÎK¢­ËãÿìÚ¼mmüŠÌã¯m8³çèþ¼Á¶Ï‡ªt³vZv Sý€‡OÜFv!ÝMlò†z¼eŠ)¯âÑž 8£Ô0`‚yˆAÐÑ/ž0Ggˆƒ³¦ÝÿöÀóÒ–“9R¢þU.­9ë›=x¦Ä fa‚`ÓJÌuânåhl¤G>ŠšÖédW‘Eùã”îŸ{˜î6uG-üéÊùkß ´Uy|Û7®ÊŒòw55þðÕùoüÛ‡¯ÿïþÑtÑ«^ŽK=ìû¹.·XúW'ówýIËèþ°ûä%§ð=OY£ÀÔ7P@- ×¼™’tÉ5ˆïWKV)Gë0Xõ äq» 6Ö„œM(a¼ÁÉ:”/6шM ~šGçðLÞfÔ§ITû<—žA÷Þ2 ÔB¿zJ\‹É#¡8íVx.—a™(tç†Ú±‚lB}Wr¨ÖT_#/—wŠAFóÂ(Ë©~‹aЖÁ`­(â;Ðýìb#üxhæ1ä ?#×lƒÍ´àó숽›€œG·O ßýþÙºaO ¨g€n=ð໪>ê 9íJDóPq:Ÿâºt]3IÎ`’V )|_ž(˦[4ņå¾°'•Cf{™ð —–öNÇÙC„…'†æÂ¶¦»%°<#ù¾öy¼ÀŽO”u:‹TD¶|íü7[xà=L#³’~ Îéøž®kPÊЀZlTÅCÕyiä˽GÚê¶Ue ©.à!"œ‰"3hÓŸÕre69Y€…ÒU{Ô½ˆü óˇ eCÐÑcÚAiïÝÿðtàœCO=þõ9}¸p@oE*Â{¶¡D­Y±ÊÏ ZV¢28(†6w”a×c°c£Š pN {€œ¨ pþ°•ìÉÖ"ªœ”×k¤hL<ú~âa%¦Aµ”e£µð›âÐ%zX„¢+§®ê¤züÀ-ÿÒG]§m:¸-³ò«­€.EiÌ6°‡Gu²1zág›—ÈÎŒ KàD‡ÓÜ=í–.yû÷¦ þ²ìÝß/zëÿ6þð?]-ß§8A¶1”Í«bA¤¬ßÍIÞ»)íÓ=…5ßî®úfgã™pOQ2hgs7'Ö'1ÜY@µ,/`¥ðã9vy Þ«3ƒ7—p Sü72øN†ej,9-ÎfHÃcY¦Äy±ƒ­y4GÀa¸+nà ³HŒr… €søLËX`-Ô`”âµÀÓé]fE˜¿m ÙŒêgííºŒÇpÎH¤PMBtÓ À¥ôP“p¶• ~PE×ì_𾇷NÝ>C(Œ„†¨w#{i²™\´£ÔÂw9<€µMš±†YÀÓ‚ß`8clÒl<­ÞÉ‹ö±O sÝV–H‹ ¶‹ ²Me‘"¬’®|?‹šS„¿™ãÒ?—¥‡aWú«?q^þ*ÏÏ<™ã.²MødE0È&‘¶;ó…[2#rý¨ILWG£?ûi)¦ý'*6 ‹(Qû<±~ÇŸÁ»í‚#*²„]Q5WŸÚÀ ^þóµ37.ÇtWTm†>ר&¬]1R‡ìBø³0e/òŒÏTlÐG1Íx~áæ‡ÅÒQ]ÑpºL±²äÃ?›8ók¥¥£§ã8‡æê^ä)ÃHV„€E€kzPü_<š‰&<(tàA¾¿N\\tঞ:ýÛ“vÂP 7<5[Q5Ú„¾py7œ0›÷á;QR¢ ôVÉJ4Gr²5#F&5TiVù w÷7#¥OÞŒiZ5"6I LÛ¥ž¨C**0›² >È”ÝÊ‘FéÃz¨G:Ôªï=hF–ì&ª–ð£û Sü¨™¸xçJÕOÏŸú|SQZXåWÛWeÒ…ôApt8JPh¥u¤–6p‘NT£>£¯Â¤õ„©gZR§:3%ªŸ¯E)ix¥A=Ø(;Pà(2ì4á>¯:ܤƒWZ–´âA;8ðöŸ&äo4´–éDA5 A¤?@#åh=lˆoœÄT}ò¡ú?žÂT%wèž‹<ÌÿZQž/ðNcºDú­M Íä„–ÉtÇÜO¦Û’øÛb¡\¨Ëò?oÊfb²k—> ´{7Œ´€N^Äõ3N wId8…{æ Þ{i}“æ¶ô«Ýù˜ü²ê~5²£ŠQ•íP5žÉØŠb§Å(tƒ-CuqJÔ;-éǦ®wÕ t7¹Ýu­¿ÓÞÕjåH3‘Ž À¦i@•¢QÂÑàôXçÔ8QuÔ¸ó›ðVbmbjÀE”øðÇ’ûçð:Íxë™bzÉOT››ŠÎÇçñ—Bò^MÔBñ§ã—M4£3bT.>ÚÊÑ£(Xš°+†P 3Ó¥{$8VÖ…´Hq;:¯ì›ÑÀyâ ò+Ë©ûÑŸp‡<ïü4ž3‚¨`¢ØC`'50Ñá×Ãq“D×#‚"vœNbŽKƒ®ç¹ººp`‡òQ=’‡3tRÚ…†É[ø ´£Í0ÔÃÓc­x]$<'‡ArÂ÷д ¼^ÇŠÚÒºÝùùµŽ–ï’Wç "¤ä˜€UY¼=[³Ê # 2Y»ÊÓJr¹©ñAA¾¦lšƒ“Í{ï¿ùÊŠÅYôþ.™÷Gë•C²7f» X””κ¢ôõÅ)Ç—žülÍÎ ñëòY[× ¿<\ôåá’4¡ç® W{ŽÜnR÷iE$?o ï˜öŠRëý†ý8­¼ ’öÂ\:Þ¡‘ô*ÄÝò±8£œèÒ©.ÀL} >ŤxP#êÕŠûàáÔ4)E%yˆzÚØô€VÚ¤¯Å¦zQ ÞwÅNãÕ =¡4|ì²VÒŽ&“ƒòÑF"Åc«zá$lëp8 T Ñ”¢™ì$]JX›ª¤yáç‘×R…û.uð:•u¸{®C‡ …5L"ë"Hê÷ >Ù§ßw4}Ä~„î. `[7r=VJ"³@ÛÎãúÚ¾´Q X³ò"Z9\‹'}w"¯Êx#¾â¤lÂ0€ÆfÅèY­¸o&Þ€©ÛtR¨¥& ¼[ÕX=¦êæÇÍÈ=òáZùX=Btø¼ÚA’!½Š±6Õhà4ÜEŠQ`þNi£¹Œa×ÑÝÄ´WEZ¦ä—``Ê+ Ú™YybËÇ{ó3y4{ª·¹»íróÅo¿þ»Wܬ–ù;›‘m–¹Z, ùÚRH+ݬç;¬| pÚÑôõ`¯¼PÛäH¯»—Îݨ¾Y;9«þ" ™FåT#èf– FŸÓp“Èú4ã“ãí3 Ò¡¤w+å÷Ι(îŸEEÿà;­F-Ñ%ʇߣR?¢zñO'u# Ùuè[lKZ4ªàó)´‘¦&¾uzÖ)ܪ'‡k¦Fk$·OaãuðûªžEÏRu¡gÉP™h¸šhœ•”)i«òÁ9xK((ˆÈÒšhV=¬VÜ?¯ÏÒ’u>!c/ oìAj¬zÕp- %Ú±¦(Ôg´8‡( sùÃ*€"¼Ï5ŠR<ª†3(vÑmÕŒÖËœ‡“è)2}ƒH$«á‡=NÛ§FZ[Ûè­ïôhzQ~ý£jtž¸»hס ®užÀ rB¡ Ø+ñfµ}@;ÚQƒ1ÅÕ³_nYöþÜ»ÑyóBNúƒb¤W9Ö©oÑá Ÿn]ÍÜÕ}˜tƒgM>x&qûœ'¾”ÐÛÁf•H}¢€*AD†b–Xk¸¢Ïî×?kÖB: Z}z£Pî}nŇ{;Ñ0(ˆ×?GfD2¨@"^ã Îà:H+^>¨—vâ†;B…Á»_=ù:ztA† 6Ü׉'¦^N"³û¹G„€ÊˆwKÄðà;ÔôкéF "íÆÛu#i ƒÚ ¶ƒr„ã”"îDsxTÞ55ŠâµP^˜á¼DÿjÐíÝ× W#®W æÒ'œ|г sÝ8Úšµ’NTìÉ#(epTŽ·ãiæÈ©ûŠ‚V¢ð µä26uG+»zýÒÙ‰G°A߀ù½› ug÷=¼ª0‹‘™´w[Æöò¤ý;²Wð7—%&DûуlÓé©qô$a(‹J¦¸2ƒ=íÍ–®Xðö²yo™.™·ìƒ×ÞÿÛ+þ®Kþ– <¯¬¸àìø´èGÓ7c9î‹¢÷”§´³àÓ=Å_(=ùiùáí9õ9±áb×—CwjÇî5?ü©zü~Ëè½ùh75â”§3uS%êÁ&¯cØOZÑ%Lñ#&¿¦“\‚1%½8)Ñ7CUÄx!M·JÔ(:§•¶`ØUXÚ°ƒ+Ç[M^Ħ.ãßF‹t¸¦@–¢É‹„ë ¸ˆ0&ƒL ¢ÿÈà›DmÔà$ÌáŒd¨1ºO#iÆ›_7¢¢1*<“²Vœµúx$ÃÕ€:D"µ¸I9Ñ@4",œúÉ™º „é’PÛ‰¾òG5°6u\P"ã~®AÚ ÌuhGkGý㤭8ÒTaêæiy“ZR‡á5$Õã (*L‚òàˆ<°âvä•£*”0QM´Á·¼Ó? Ë›ö*ݨƒ›öºfXñ:ˆš»WÏ_ìøêÇÁS÷ª½ýcÕ©ã›öïÈ‚›dMaDÓ‰Çp¶^ùÚ²ù¿ót^ƒâÅñ ò°ðu\Iq³v0žæãH÷vñ°òw1aøÙš.üS²À·©òàØÝ6øð;¢H0qJPõ¡ÕŠ'mÉÖá@° UÔX|mv€2 Sõ 錒&ùϧ‰¹âÞ÷È?%j@¾*€@Q:/½£Av§‰\i‡æa "Q+P (0Ñ>ªE‰ÛäwÏâfö˜ %t´Fzç4E‡æÑ9$¥m8ð€B׌þžúàéPü|Ny¯ me´ ÓD+ü#Pl'ážFFÝ©‘FôO%ÈL$BâUx‚°ÎulÀDª"< þpBÈR‚yàOÜ"„žBdŠé/зh…§À3{TÝÒûçŸõÒ6¸^Ÿ_†`ƒ nÔÀ§®B!L²Våpô¸Óþx·õèùíé-ûrO­nÜ•×ÿÕV®ó’ø`‡L®W”¯Ãy‡d沄b=ÏmÙ«Aæo¶|¼ªjkÜùM‚¦Ýɵ»R7Åz'˜¥Plã}­²h®I¶ñ^f…LrÝ&y4§¬`»ÕÒÆ(ßb¦K.Õn Û­Œï¾5Á§$Ü6ÂåÝôPÓʃ¹å™”œh·òBfÇ>’mŶ,ÉJ¡$D{ÐüWÆòHB¶ Ýo%‹bA÷3æ[ i‘žG÷æÙššŸà›ãùÅž¬åq©ÎÅ ¾kR)Û‹Ã?Û™v`}LEqøöÕ¥!¹ ~yÉNÂpgÓ.œa›ãàYx/÷ó\ÍsÂh ô^L 0pìaÁ²õrû€äø¶¯û|Řd×ÀŽp7iÌý<ÀÅH0’c=£¸Ž|¶]\¤i£†mQ;ˆ·:µÕ¨BT»%Žz ònïDU:•S£¥n>ûå:¤ŒH;‘½NÚ<~÷”z´íòbDàÆð”‰»•?ô~tïú·Ãw*a½~(ã“Ý)7¿¾Õw“º]L¸ÏÒ,¾kb˜-L Ë!–jÊv^—JÍö¥¹.rXüŸ\rݱ­_lÏRL£MÒX) ;y!Ãm~”¿i¤ßÊ¢h?ÀH™R6×ÓÇì­Šl.6Ò«lz¢ÕêÆ@)@U§à†ÇÃŒ[ÐÚ—hƺ4cݨk€¸”å7ÿôʽkÍ×Ïéd—AÉÅkü†Þ6-m„¡ªR<Â;¡ÀN: ›o×\À£"¨^Ñ‰ä¤ E¿€ðDƒ[of<ƒ¸j‰[{žâ@#bL=„âI<…ð ‰?ý‰¤=þ¥ ¸êð(»z½;RBðU^Å ý£ÿ—¯·€nkÉvïëî7ÓÓ3·{¦§/nØqbf’A¶,°˜™-É’ 23Æ;ä033“1qì8ÌÌ fvìXÿ®s|Óý÷›ykÕªµÏ®]uŽàT}E»ðó5PÁ4¨§ùá,èݵŸC5ªWÑdZÖø[.Ô™ênFUP;¢S|ýèQƒU}ïk0oh1$”€zs˜ͪ£¬r“Û¡ñÂÂÅŠ& _‡F ½)ƒøÐ×FàO5z÷óë4/?r­1¶?í» ú¯ý× @ÿâ¡®VL¸ùàêáº#«Žì¨*LUÆj¢%¬œŸ^]’R™_]œ¾²"Ϫ,-ÉYSY\œŸbTòÈþ1 Éß1ØcšïÜ sýsÛ”)ÿý{F˜{¨× Z°‹(*PÅ ·E ô|!Õgu¥mó²”˵›Nì®Ø¼4m£ÛËl.¬=X}x[ñ…“k]ÝßrzØ\­ßòðòg·N=¹uòõúî÷WÐ"ØÑ§vû ûØÓq·B_ï¡Oýõ& cðÑîê í9ƒ7í#wG:[‘g¹þë£sz¯¢xàò¹Ôw­ÿãù¾ 3}muÝÏâKÐñ€/“ÃOaß+¼]'ÌSÄ94<2ЂÁÖ„íÕ@<ŠANïçÚž÷T#]çAîý€ž>ï`' r_ƒŽš… ¤ó2ÄýŸ/ö|jÆŽ ½g{†¼Ù >Dóž =ÂFÛö}¼üâîÉË Û.Ön>¾gá©}Õ{6¯¬L@]Ph–³ýfþü^£¤Äj™NSÿƒ4;7I5/#.rZ˜ÏŒ×I³~€Ÿ[ÂI6I…´N„g¶U^žstÛ’Ö3Ûß?hü5ö³×wÏ|í¼…æ^ÇžŒu_ë¾´ƒº´h­Â%4ÄÝ‹\Ðëùb½ØûæôÚ(Ýô·‡WìC-6dÑ0ƒð¦«aàÍ1„] ãÀÓÕ€– µ×‚Kwãà›Óˆs:Ζo ¼>…°¤³ ’ àIø‘î<ÀE@8€IˆjúÐÀÊ¿KO¤gPöözK ð¨}¯Ž¡@æ:B=”:|°ݱñÏÐ;TK¡Á¥ Øêž3P `Žñ\ØFõ:|Û»½ç<Î3ˆIðº´wüÌëñîäo]K0ï]b6€7ø²jPA›ìÃêm¬6c¨Äð’ÑíÚ{_àk$´kF£ÜЃî¼vaWù†tÑù©u‹®o-}z|ÄgŠ2ÜÅ&‹01}õ7 Ù€G:'Ês‚¹6í^Ýš ‡«ö¯Ï,ÉZ4Áj±—‚ï©ùÆiFu8‡æ¬“báza I¢æúZ•áÙqœµ(Y§IÒF”§Š—hr-QéѤŠ4QyªðÌÎÒ•ÅúÅÙ `HZ:OW=/Ú¦(I—e%pch&-1!†®H¸Þ*‰¿ˆãÎ&Ï’°]… 'Ô?ÉL…gàRgÛLd¸Ôˆ}ELgÍ‘9“èÿ-lŠ˜åšIøI ƒA¼¹¤7½,@)ðF£Ä˜¿VûÛ—×·Ô-÷p‹Ç¸‡|íÄ×ÎKÀŸˆd¡=mÄu¯ô½;O œx¥vr@ý¡µªÃWPg¿c¹Ööì0üÐЄ]nXº{úñ}…Ïîî¹r~M¨ï÷÷?µœ]~«i³„ê`ày¤êC‹âi©ZÂÂ,áòBEŠ&BN ECwæMJÓ’2ôd-Ã4Ã* NRDDE¤ƒ’ì`æyÙ¤Vž¯.Ê5I–,'B !U) žU/úú¡ylh?ÌóÛ̈æs±eEhú8C·F».Av´ç:àÍ™ƒ‹ý=Ôö·/íGÃÈ÷ÐrÜîóÃhSÀXO]ÿ‡c½o¡%"ð¶5¡Y’ÿx«`³Z4N5¨‰EÿV¿ÿ6ÿìøñ”è°s#hï<Š‘3:ÔL MŽ w4 ]KøžÄ0Mز™:¨á¡zÇÑh®0áob|7D{þAp¦_1çØ8¹á6‚õbðÖäŸó[倆pqÜž1Øà¥ßÖnýýý#­!,ĸƬ†ÊUØt.àJ èÞ·§ðºê¼zÁ{^ø"ùñÑãßF›ðtžÃ1€w_†õ¶¾ _ã1>™8>’ö›ü¿­¸ÀFÆÐl/¶Ž MáA×âös#};ÞÖ ã௵¿;Óù®¶ãýÙö&Ð ~Fî® tžë¿Òöº5‘ã‹¢_ÚûØ¿…xÀË2z}>¢9#`àh o†Þøúù,>Ôƒ€ðæ-¬Ô J‚j Øikìy~ dÀ Ä!m àâ}yùt7Ã%, Q ü‚¿ !K4¢{×@*^šÒzsÂè§ |â ðH ÏGô _?×âóhÐõÆ7jAÍÏ™oŽÿyÑr7Ú©ÿ<Ž ãŒO]ã֦¢ 8çðuã€Ô‡aœuÔxÕ„Uû8áC@XýÙß0:ìRgï;×÷Z¥ûð½¦í¥•ºðêSóÍ·¶—>;¶Vâ;YD˜eæ(‰s ȉŽÂL%ɺó6žÿõ}UæiöÊŽ”ªÏ.MÈXéžb¦”À1O4É%_A¶P<–&ˆxRØ>…Šðr= â\Ipž<8W”&ñ¶²çÖo/¨Lå”$³W”FGúÿ •ú¨¥~Zy”凌ëä!Ü(ÏO/Ö -ŠðhQP¼†œÇ‹W‘¬ŠÐtcTŠ>²0Ž—e¢ÅÉù±ŒYŠÂxVI"§*C¼cIÒ¦ÊØµ ŽYž.5Irâ8óR%Ù6žA«'Ǩ‰‚üL©VTcTp€I¥«„ÞQÄ©À?ôði8ê€ DD ™, Ï:ÒJü Š «Žp qìþÁGÔPœ\oYU²Ân¿u¬°^Rÿßc´i¥³u´£u°¡»q¨ ;aðNÏëfi<¹stgÐ(W kÿüÖŽ·öáGÙ¾¾³ûÜñŠý›Ó“Ì!»6¦ØGo¨Ä.s¿óuù—Û-›‡>]H6„ØXyñQ‹òø+J¤ÅÉÔd_†1$UdSùçXÈ*úÜ$uh‚"Ħ –&I·-NÏ40xÁÓõ ÝIIžmd»gê"2´á¦‡,Ü!ÏÈHQ„›Ø>à"pÊÔÒ†ß\€wçk'rÚ€yVÇö'ޝÚmEsU]h¬~ íbïÇ c}7žß=âáðÇçwNÜ»¼w´çÚH'rg7ÚÓ8Ôq€ÇÞW7ðñXßû£Øff´!uŽšÿGà†7!uºhã þgàùmäç۬´ÅýèèžsÈÿLw#0Èð#âòt¸Úëú:ê ü‡Í á]Z¸ûÿ?ð , û'àÁFœ°ñ™ó88á̃Ì·KÔAÆŸ'ìób#—××TùÀ—<ЊWAøíÆ+%Pö6ë|áŒô6¿ Swè~“q?ÿcŒf;±…"]h÷Àûš/ÈÛ!r¤ŒF6FnÙ‡o·¿9Ûó®Mg  “¶†Û[Ðôq×%4¹3|ûK÷Å‘ÁKíŸjûà€owóðÀÕþî˽Í4=‚æõöµ£ƒÛÞ·Þ¹~äå“úÇwÎ<¼uj뺢›J÷l­XP[Yl‰ÑR2Å!~“ElŸü UYqÉ|„eU©›–&™øå¹fh4Õ‚0 ÁÑsöû»ü2íÇßAc:ý§ßÏžôGz˜³¯ÓOrA ’ó"Ĭд8U¦MÍ£ù«Å¤Ò¢ŽÑ»ð’"Þ°?„¦ šÍÍÑÞ+_û®bó‰­h0mì~ÿç{÷-D\_ž~ù|ÅÞ‡9Ázlï¹dˆ{ïهٿ¾°}†ˆeàáXÿ´Ñ 4p9ò „¾öÏîŸém»>Üs·¿ãæÛgw¯¾t~GÝÉ5Gö.Y¿¼`YUfEa|†MiTÑe¼P '3Ò“Mñd<Üfÿ•0S/' ™þRV EÅ‹ôTr‚õ¢‹ŠšlâJ¾DŸ)¿|ÿ‡äj‹fÅ놷ûÌÿ úLæFºl_]¸gCñÙýKw®-<°©t^ºòܱ5m/Õn |¾n}i~Úûö’}èÉ×Î;ƒ¯¼»jøÓåŽgµˆp:¯Bÿ´ÿmãЧóݯN|Ľb 7ÔÞN·??„mG:?øîÌèçÚ¡÷g»žÆ–56ö¿91ÈhT¤=ŠyóC‹pú^G‡Uu6?  æ˜?Ä¡ÿ͉ágÆÚë{^ÅvW5áÛ!¡œîÇð]“½¯N8µ7‚¦ïõIè-‚_Š<ðötÿ›Ócíçú·ÚÐ,öÐûšÀ›ösýð€0lö©~ôsÃð‡ZüQ‡>œ|bàÝñÞ×G¾|:=òùLdzý¸0Œùð¾-ØØ{ûÞÅ­Çw»C3lmó—¶³Ã`v²×à ¶±½æ[· ï+“¾ÆòŸ€ã™ñº«sðíó¨ß7ty\þÇz©ç°½ üxz{ß×¢Ã:‡ïÝ?½rW²iEʽíÅïN®~pp©Ä{ÂÂ$Ù‚T¥‰î¥Šp”„ÌâN×R=#]~°2½î]Þ²1ûʦÌú¥ñwVæ)ÂXî?Ʋ|s”ÔdAh®Š–"àÉ“GæÊH%ú¨d®,Õ5ST¦£ðdðýÓ¾ ˜‰Bwuäô…i¼íKâ B›r÷oÍ—ñ\­†ˆÔ¶Z(ù4ár¿ZÈs¤ÏÓ½”\?£8 ˜Ç,'Z•áõ—gDzcÄÁyqÜ¢DaAw~š|i.N°±cÏ;ž]pŸòï¯nµ½ë‚·ìbßû³/ïìZ]¥9²5ãrÍ’çß‘üÿ¼eu<“ô‹Œ?;#1²¼P¼em¢€>3ɾcMŠœé˜¤ ‹‘zf[BŒÂÙbò=ÏÁ*sS2¦é¹sæ%Òãþ)Ú0‹ØOÏñŒ“†,Ͷˆ‚d”9Ž/h’„8=Uš¥'™¹>zº[šŠ/"í@ŒQ' îz\óåc¼ø‹ /)šQýP ü6ð±¾ýåéî·õýÏ|lîzw@n¸óJ¨÷ß^Þ;ÑöªzvÝoÏbˆÒôµ·[Â0pr^¥¾ x‹‰Oaœ€#D>y´Û‘O aœ€û)jÁ¨cü8<Œ‘šñé!¼ l_@=Z„ÐÙˆ¶;õž‚: úS]ç 4¸ ÀÓ×^À3ˆF~Ð*Dt—ðcPé}Û/ðm×<î/w×3¾ ó ñm‹=žýŸôh÷¶@—qŸŠßvq¢vû‡à $ îÿPóÍ…¤â»¾Ë…g‡o \@N½°£íûÞžù'j?øá* û ôhz ó3]ÑoÆ`ƒ¶²b—¸Í‹½;;„-{<ÜZº#Äp‰çÂÖ¥ÿ]i;ÿ?´ÖhðÄ_;à?pÉÞÕ2ü è¨MÇ£éÑ«ØTik÷›ÆÞwð»ßèÿp.¿´_BΖû ~y´óJ÷‡FûèÕîîúøqšûûš;;:Ú{{š¿ ]ýò ü Û>5õ÷^mûØÜÓye¨÷6òÌcÙ÷ùò¸2òl°ãº}ðQç»–‘ž»­ ›/ŸÛ~÷Êþš£+6­ÌX\fYQ•H#:G9–äZvo^xh×òM«J`ÒEY¦íëç¯[V8/;¦0Ó(`Ð#܃½g(8á’¿ŠG’±ÂB½fq#}b57”Mr—s!DMõ™¤¤ÇñRbXËæ%,̉á]ôÜàõ•é‹rÍ¥)š-‹sV—%§¸9(« bW–$.+Š/NQ®®´Ù ”ôXNn’`áÕÛiRXÀÅŸå/ jeàr¨3/ÊG)&ŠÙr~ðäþþ;—©ßëDa…©êùy1yIŠ==Ç&9¸µâÚ¹]¯¿\¿}纂v÷º‚ð †ÝÂ×|rÆ>„6dÁ? jªÞ÷_:."¿å½—‡Ûá5¿Ùûº~äsˇÇÐ.õŽ–¡ íώË€7ʃÏçô½;í¥êFÀ€–å¼;…Ιú\ #š9¸ ƒÇP‹O<µx:fÐPOGà ¢š®&0ÆVƒpвwž´¶¹ûh0ãó@5Ch×9ÏIìć ]Ïv=?Æ€7<3²­‹>ƒ¶ô´ #…h‹ÒCÏ¢ (ë Ôá ÒpªžÚézq?0ôóã=OO#ºÀ]bxƒÎ(:Q´40†5ßÜø }:ûmHwu8>{õÛ 1îÏ’Æ[Ã9BݺOOÞ8·G#¼l9D+äúüôÐ…SUPõõ¼=…nÚyí©ïoêÿÈtÑÞ}íÆ‘ÅûŠ5'+ ç'<ÜUÞ´*Wæ;iiºzIºÊÂðŒ‰r¦º+ÂçXxásÿš£"½ªß|qSNÍbóöláÓ“KÇó´N¢s?$CJ*6p µQ6N@–˜Xªg¦ Y’Ðlqp‘ŠTid”j)EÊð²hr†Ì?ßV–@¿xdÁÛ›–—hWTW-Ž¥„LVˆ¼,Q ±ŸˆëeÔFÕ=éF q3|U‚ %7@ÍÒièÚÉ©&†E–if§DG¥™¢æj—E¯-5­,ÐZèy樅ºòT¡IäÀ“af$êÉq:R¼œ›"ŽÕ“µ’ Ø_/ bV‰Q‡˜TÁøÀÐKŠ5JÎó€K@ íÞ9”@58—:[Ät†$£’6VÑ Âüൢm £·^ÛÜpl>r8ß{'œá~ ‹C(é»0Õõ—¶ Ø6Ÿ«ö±Ç£m·÷­­pŸò—W7 ÞŒ¶_ü ýE€¢}Sw®JØ´$fÖOß}¾?s¸Ü¬ñóqý䟾óvûW¥ÐÕÛéá-Éä'h …‰Ìxµ??ògnÄ_£E³!)4>%iô У{º)¢(‰e‘ùSý2Š|¸aÓÍ"ßduHœÈWGwVQ!Ö3\Ô'ÉQ餡ºYÞ©Ê+?HAr~wíÈð»ºÞ—G»Ÿ|súËûx} ïÐÿ®öËghë‡>4ÁÃv´¢Ó†î|la†Mm9³æý£SÐÙì|}ÖŽ¾šq§ýç‡ÛkÑ"m´&çòoçǵbÍ7îkzÜuhzßÕàŽâ18­Ø‰ZOÅ—û~‹±M‹­Š4alЈ¢´°³û<îyb‘‹¹Þ xŒ;Z=:t sº;Ò‰í>¸>ÚÑ¡ÿ=Ìǽv ¸-G DMßUôËö^,¨—™¡ì÷¾ÿžf# ü t_ ÿæd<ûofP>ÄhY{Ïe0ÃåÐÇD}O¸ÏÛ} Œü Eõ¼A½žo·ÐôÄÇsx $ Ãïø¾(³ï]=Ø€JˆÁ÷a‚çÂlð'Ä Äfü‘°Ï2þÑ0-k‡ßù?øÃô^…ø+´>]—ìí-_>@ײuà}óhû•Á-]/@þ„4c×ìýwì}·‡>^ì{ÛÔûج ”c}7ú:/ôvïî8ßÓÙÔÛuâÞK#ƒ7íc>½kìéhín¿Ôß}­¯óúÈÀ½þÎëm¯›ð}Xc½·‡Ú¯w¾iêyßúîñÙ‘®[}[‘ç¡öÃ×@c}ñìÎÙ'wê;ßßzpãlýÉ-'®9²gùòúU‹³W,Ì\\ž¼¬*½(Ë`TѸdÏc;W¬¨È¬Ì‹_V–V•Ÿa•@(ÍŠ†Æº,[ŸjæR Ó}æüEÎñ.J——¤+㤥yæ8ID4;p^¼4ÇăP™¦MQGñ‚9AEq’…±kJlÕ¹1‹ób¬ŠÈÈ€iV5­(Mµ²2uí¢ô5 38$g½˜¸qiö’’„üdye¾9FAšûë½fÿÅf -œgX>?vQ±QÆöøé?¾óuú0)ÉDK1ÓS- ÝÅËñOþ4B?%Ï—IpäÝ|fþuú}GtÿUÍ"@P2¥T_n˜+“0R…$Ovˆ3ÕoV¤Ï¬ ·iþnSƒÜfzL‡ØÇy²·ã$Ï9î3Atæçâéà>ûg¯ÙC|¦üÅÛõWbà\·)N3ÿJ𛠴âù°¨žQ‘nN³¾ šgbægk6®Î?udÕæ•yKJâön,yÿ¸ÑÞ÷È>øØ>ò|àóÕÞ—ì_!’hqÔSÔüm»ß÷¹‡ú*ˆ?==ŠõPh¨­¦çÝ ´´ ï„‘ÎÚþ§;_œ„ÿ^çÓ“hM~g‹½íIºh{|sÅP-ºn²wÕuÔö¼:Œù½A#<€À!=/c#Õº_œVés w2îÌ ’° çz^žÀ]@@Œ€¤§bD>ÝhÐ $Ò m]¨|h@SZ¯Î í-Ôóâ<Äl>6B,×ylËy=~ (Üb´ù¥í?Å·_n„Ÿ(1øþ>ƒp°¡{ø\9À<øÀ|Rxº_¶ž˜Á ߺþ u¾Ñ¤œà"¼¿%Üg~ Ý%_>7à>lÁë~nÀFÒZî_\¿sUnJèOá6¨ÖÝüùÉác;³Ñ>ôwg0ŸÏ5£]u_{ÏõÀÝvaìSK͆œ -פ]^öòТ³‹’¸ÎÿMv¶rýyÞ´³ug9Ñ€‡0ó?âx~N¯9±Ðº=S°Ø~cßü\U¸"Ì1Žd ye+(%Fnž†š& Ã6m‘2Å!%ú¨b¥H9ßÈ€P¢#—›¨iR¿tuP’Ì¿(ž® 9 )3y‘3(¡©¡¿²¨ŽjAÂ÷²<µ²£"Âffëå$9Ÿ ‘!øaG:3Âç ÓhÁ3Lrbº™nfé… Zò²b˼$‘M¬a8¥i˹ɚ0«<0Í@)J$ESŒò`½$Ф ÍI™u‘"¶'Ü1ZNPp=L Bf<++£ 3œel7jðd¢ïlÒÌ8]x´4@Å÷2«Bà’1CÉóäD΂ e¹B=Ø$D“¬š°3 ¨Æ¡­ÿøè(Té÷[·Ô*ÇÛÞ ca¨< ÷]é¸b¸;ôé4ß›ûß]8½»ÚöO6¯ñàf{çè`B§~²޾¹wpßú´MÕÖ½3WÌ×íÙœ•d% سr3X™) •ÔC-v·™ÂÒ,$yªYîk–yŪ<œ©FÉœ kHZLPŒÜU/˜£æ9˜$nžS¬Ò×¢ðQ³Ê³„J¦“,ÊÑÀufºÄ }Í€•Ønu@»Ã>x×Þûýƒ“¾³ÿTœ&=²½ÄÞ{ºçö¾kÝoÎØí·qcظDZ·9tó›+0¼}G[8;/âÀ€–|nojqxÀ9ä›'1Ür?o ¬´q?chMõÕ/ÍЕÃÈÀ3_»[ÑâCüÀñþ«  CÆásfàÚhàÚ+´¼ýïáÍÅö”u]pµ÷Ü4_;¯ ÃnúoA‹ zh|ÁOEú‘Hß “C®» i»<.÷ÝD-x7¶N`z£·‘â‘(íe¾ƒâ.lãö—‡Øöí;è(+Ü~è>²„xø*y¹žÓ øË}”âáè.Øבì *¸ :‘êJ}Œ¶º`σîòõ*l@¼ƒö ÝE6 ‡\ ùúÝl /žŠ—2Ä £ÝåÑþ£¬;¸wñëççìö7_‡ŸŒ=þ:’WÃÀ¢Wí#OG»o”ä¼zp¼óí¹¾OÍOnøôòÌ×¾«ïžëþPÿúÑa¨@Úž„.ϋۻ_ßÛ×÷þlï»Ó€v¾tÔµ¿<8Ü~¶ížÞ÷Gí} ]o޼º±käã<í-ƒoëºßÖ~ý|þó£ƒh?#ô¡0ªéz~xøÃ|øeôs`ú~{¦íñ¡Áwg?:žd H›¡÷5 zW×õìÚ5Ù~ ÞÔ téní}y piÚ/´=:6ô®ÁÞseø}ãÈÇóðTý¯kñ~A÷óÓð„ÐM€§íyqÌàQáv8€¡Q ß€.qà Â(±}èuýo€»ÐXšhÃ.Ž|ªÁ¬†ð-ðíõØHÔ¹îGÆOÈút_ÃóGKàgl};WßÒœ†¶Ì£ÝßÐ<õÐÇüD$»_îy} ËE>u1ÔiÂÆ‡íƒW_Ùºei š¿îi±]Çm Àž×gìý— ï±í9Ð ~jÀÖ ÖÃ:Ô^ßöâDïÛ{û¥åÆC冫³oo͸¸ãæör#Ñ!]šMÓ†;Xèn:Ò\aÐt37 xÖ÷™ªˆO­{j—'íΗ®NdÞ?º$Kfeû•Y…:²{Ž’Z¨c& ©âP+Ý;žå—##ëh™bB"Û»@^¬!g˜‰bß aC™Á¦ÐsÜÂ<ÿÀà ŸnцËÞ:yˆV¬(ùþ ^@œ>J. ó*Q! s¢Ud£†,áúqh.yX¢‰QUd€~Ê¢â˜üDAš12ϵ$OU–.ŠW+Y.¼`ªY\²#6—MžM‹˜¡W„F«‰2¡ƒ2[ÌrFÍ’!M„X+ò…ç:ÛˆèN±Z"èÄi9‰\€™È@èéøA’œã°¯°" ²@ÈhQ‡ê%þÏj¶îx×mÁ‡G'¯Ô¯é¾úòþÑÓ‡«¶nÈ8´·äÙ£cƒÐ{µ?ÆfÕŸ£§ï:¶rïîXç=«KÈÞ*F¸Ûäÿ‚ºñÙµc—k×´œ®2ٸIJoCæêJcy¶Ä¢ò?±¯|÷æ¬KøécU;6gÆ›‰ôˆI¤ ¿™ä>z±‡Eá'g:X”€s®Ñâ¹JÞ,£ÔE̘¢áÏNµ§ÆÌJ¯¹G‚> 4“]‘ËO6âÔþY1¤l¹ÄÆhLV…–%ñ3£)ºkŠšTž,)KÇðýMÜ€8qh…wéÈŠþÇg>^ßóáÚî7—¶÷?>5øüìãó_´î|w}ÿÇ[Gž6ï¸tbyýÞù.l¿|j•E0ç/.“Ÿ/øòùjA µ?íh=´%¨µMö'Ïnh:¹ííúò­:轆üf|}€ š-°¼…yÒx„md»Š’ðKøA—`Ö]‚— ÞG¹Fîá«ÄQÃÅ‚d„¤Þk@›ðÛÇ"e÷•çÁòK[ H‚»|¸`ïh¹;ðî òÚy÷kÇÍžW!m»Õ÷ö"´}ð¹ µë%ðØ£‘¶ë`Íb÷Ë–Oë>?nì×úöîÙáOÀHÀfèõÞ7-ïï×¶=mè|~aèã•þ·—ÛŸ5‚Üñü¤â6pÇë»_]ù|³çuóëÛ§Á¦ïÍ¥7wOAÞg×¼»WóñQíóëÇ´î»ß²ÿñ•ƒÍ'Ö½¸yìÅ šÖ3ë¯Ön½ynûs;[Ïn¸T³ùƹíWê¶6XÝrzÈ玭†øüñ5玭¼prÝÑíå'w/¼Þ¸­ùÔúc;*m)«9°¤áÈÊ3ûÞ»¸þð2ˆÏ_UwhyíÁj°ÍñU`sxkù®5¹ë&ÙVV{p”Ss`)ÜbH›–ÓëྷšvÞjÚ}ÿâÞkµ[¯œZÿ iï­º­O.¼~vÓýó{@¾~vËÙUG6”@ûÞthåÝÆÍGV_­ÙT»oaÓñe{×eC8±³tÇÊ´«ÒOí.¯Ù_uv_噽óoßrµn}ÃáÅŸÙSqåôÚ›u›nÖl¼trõ•“kî4n}p~׆í-GVÖî^xvû‚º=Kêw/©Ý½¸aÏÒÆýËàF·Ïm»xrõ¹ÃK[N¬:dYÍÞ‡ªï7ï:µ³â잪ºý‹.^ûüÚ!ÐÜoÚq»aÓãæÏ/í½në³Ö=Îo;·áµÓk÷-¸×¸åVí†;õ›n×m¼t|Ä›wÜ¨Ýø e÷Çg^ß:öþÞ©Ž§õ77 }hm»üáÁ‰Þ×öžoîyغª‘×·w?«m{x âæ£Õç.|sóÐͺõ ·[ºuq‚€8µ$‰{lsÁŽ¥Ië*bo.ˆ×D& M‹æ™ SåùÉÒ ÕZaÏœ¿½ÀeŒ"<Ç&™îäöƒŒå[š¥«*ˆ)JSIÞŽ“þá7%%†³ª*eA¡yY¹m~žQÍ*HQdÅ‹Ö.J_]a[3߯ ›í<éw¦.g^[™T™­Ýº4+ÓÌž—,;»wÉ©]‹Žn­¨?¸âzýŽ]«‹¯Õï»uþPóÉm/¼yîàñKë®kvùËç£íÆ: ßÙ{žÃ¥½ï¥ÝÞfý`··ƒðuèÍØðk»ý“}ì-òr<ôôëð³±/OðôBí$<öŽƒ×ü9âÕA e±sÜp¿‘ÐgÄ–gÛï¡-üÐãDä ]KDïý·Æë“a, \±Þ@“ã£7Ð&4é µÄí±Ï—P¡½Pùˇ&{â ûd¿>ÖÖ ø=- _kký„œ Âô]A—Ý­ÈKô¡z/#%ÈÝ­}¯j ?ˆü ¶£Ðõ¢¦ÿmãàûó=¯ê@—Cp»žk_>5C×äî—µ f}o ~wïÈçÇ'A†,À.{_×CR÷«³mOŒ´7t½:Þ‹&zБèøn|gSß»Ó/Žv¾<ò·9&Û›ånަ˜<º[¼‰^œ«)ÉQÇESh!SM¿ùY’¢$nn<3Ajøªøhó—€îlQ…ˆøž ÚÜ I\–%|ºA¬ä¸KèN:¡šç ²UbVtáz‘oœ&LÅõ`†M•2œ³ãYifš‚í¦á{q"f$F“ €q´ØÏ¦€¼` …pI3·.K^R «Ì‘//5-+3Tå«·®NSò<“,ä‚tQf2»´@•ÎËHâdÃs&ðä©1‰œÂdNY†4QÃа´ìHŸ?EÌ"yO &ê…‰ž‹UÈ%M“2æ é³ÕbO}–Bì‘–HcP¦ÐÉ¿Æh JßÄb¬&Ц Ž–¸ó)“Õ‚Ù:‘³„9Õ(s–:ë%N¡c¬Ö›KþE5YÁɧNÐ&™dî1o^è¯R²~ØT^èšïÏtÿ i:Jºžjâû ‰±’ÐxQ6ÛÄóNŠrùžçóÝõÏD‡?Bð™ôaÆ‚gþKÀÔßLÿ=aÖ¿¸þòǤïX“dd§ RS ŒÜ8Áú) rõ6ôg­ŠàY?~g‘ ’„IÑ” —ÿt™ò»-]êIfjrtüši&r¦…–¤W‡€ B¾m–XäVEŽï ¿¾–ç-ôc5Ç B¢Ž§ ¶iÃ}! °7Š}!‹†ë®xñ§Ù4¡Y&ŠI쫌šË@óz:ž‡ŠîI:¶Èj†3è|/«Œnb™ÄafixJ4SÉôe‡:J¨Y„˜â‚Šå'"»À vàG8ƒ±–¨fûC.9ݲ#]5œÃG@rÑñ‚ /Ü `l„YážA€ì´€é\â\ªÿ4H‚\ C øM%ûNrèA3ÁFååÀ@=ÜJÀï$ïÉsþJpþr±ÃæPü§‘|~e†Ìf…:‚—2x0–7Ü ‚Q¦`ú»þá=YÎðQ±ýÉ~Sé„YbªØCDºÂ%/™Or‘Ð<…d74ªà?Cœ ÅJ3F°C˜Çÿ9½†¥^/VsÀJ‹SQò$¥)ªu”UDÔÐ}Ó´ zV ?$ÇÄË3 àRAñÔ³R5t/XÇñOAþÕ5\o9ÃÅ$ Rs< ¢€äèH«"TÇ÷f†LS爩N‚H³,Ø ðOS‘,?IÈ,†ç/lŸI²°Ù²›:Ò5^¬§yš~irr¶†ž¥ŽJ–DÄpü£ÙÞj†k¬$8=šœª%Cœ¨ —SçZÅNð iŽžã úŠ4I–‘' ÒÐæšy^ ’=<ÇÃ*ð!U+ô-²2r”ìèÈDYš:ô`™®‹Œæz¥êH™F*^–"JPàï—¦€:k…ñÌT1I’k¡¥éÃfËç*fÒ£#UÁ Wfˆ«óUkÊŒ+‹õ9fꪒh°É¥ÏOmZœ°°ÀPš¥à©Ì7”dª« ŒGw.¨ÈÕÇjÈ@;™qü”ñ:ªF˜lbj!‰¶AiVR’ü8-=Í,ÊNfÆ‚±¸2Ϻ ÐjVЊ3t)&ptUªr5YV–Žï»¶*¡"Kyhsñ®Õ9JÒ¤ óu%--6m[ž±{Mî‰U jê¶e¹ËKâ+³£˜ e¥iê¹Æ¥ób³­›†V”$‡Ë›4'V¸ Ϭ•’5rJŒ–oÆxIqâÜtm¼…[Vh.ÌÕ•‹ eE†ÜLEq¾¶¢ØXYd˜—¡¨ÈÓ-Ÿ¿¢2nõBÛÚÅIË*ÌùŠUæå•¦U -;Ö§m\‘°}]juytÝ¡¥6ŸÙ³äضù§v-„‡?wtEÍþÅ ‡«›Ž¯¸pmq½Z¿îÅ­ƒ÷[w¼¼}èÉåýÏ.ïؼëÝí㯮Ö½¾q¤ãñY`מçõCo/@°wÝû|¹ëiMÏó:{çåáú^Õ¶]xÓÄ Tóõs èî¾:Bçe –±¶‹#Ÿ[íýw†?^ÄÆo}ùÔ Ü‹Æ0;¯u\0ëyÕˆR»o žu^ƒÔqãès]ÀmP–!lµ =}Œ-COð&òáŒVï_ç„ì_®cc#WÑÖ<ˆ‘#ÐV$ ^A›è1¿LèðÜFú.Ûû¯ ÓUÆî" æ)ô›ËPìH#4DƒvF^ÆbÎ#0þr³ÿ]íÐÇ0€xðC=÷¿«·wµ¢1ÿ®+€p8³¤€ˆ®ï|9¸ŒðÖñì 0  |ŸÏ¯ï­ÙWnÿr.! 4íÃr®ùÄ2ø6ÞÜ9:ø¶~àMM×Ë}ïkß?:9_Z×Í<-qkŽüÜŠäåÆ'ž^œÄuý«.bv"ßÏJsI`z$ñý´d'›(”å=9IL¸¼§rs–t‰6Oâ}xa¼‰áaáB¥a„옟Re“˜yº¨š â!ÅÈ€ZRv„“(Ê3Õ̆@[(Ð üx1RB‚&jZ¬£€š?ÐËnP'éÉù6>4‚6-i^ŠXÍñR²<@“af@ `?J^ò‚>Q yóãy ò°< »0–›MKÓ’ã¤!%6a±MX`åäÄ0Ë“%…q¼tÅ, Ì3³*ÓUe©ÚÜXYiª~~¦)IË6‰H9VéÒ"[Q¢º,-:Ë,¶©™)znœ" RåŦFó mª¼8yA‚ô *$'kÁbÈ’m‘äÇ+2LÂx%}a®.« @Ùá.%F™#Íòâ¤t£À"¥€>ÍÀ7KÈËæ%.)ˆ‡»ÌKÒTd!#%ƒeeV ˜áJ°g†¼`¹¼4¹,ÃoSVæ˜äYul«‚V‘eZ˜ r¦E¼z~úü쳌’Í›Â$u†Y–Õól)^ª‘_žieQ²âe%к%‚Av¬tIQÂâÂøô!”öU¹–œ8öPøºY;Vo_1l4ðC(ŠSuðÕXW¾kEѺùiÅIªò4|½ ²M ,+Šå’|k‚’–¨¦Wfª b×V¤n]š³mEN¾Mš•— )NS,-ŽÛ°8myiÂò²¸Bh=ó +ËŬžŸ¼ª=ü èú×vªÛhéøð}ãrÏ 4ª_Ž>ÂÜ,ÜA«ÊñEæ]×¶î´}ŒŠ%Xâ¥A®±'¯o^Un²=µºŽúnÚ¿Þ·>èÿÐŒ;n樉+8 5gƽ=³f¹Oœò×'GI”øNzýd¢:s½~Q‘\ÂþR`ˆzZ³þÜÚ¬K[ò¶äHš¶'IBÓ””DqXŸP¡ÍÑÑ3¤T)±N§ .Næ'q€¬à º ÷ù!ÌóÕIå"¤Ì5ICâ5‘ Zryº2'š‘¡ g«i™JJš"²ÐÄ)Ž€r~’¬"Qº(S»0C Ç–…ÉóÓ”ÕyÆUól«‹“7T¤áMÛŽeËæ%'«KÒô‹ c¡}L· ‹Ó£Ë³c6/+\·8kMe Ô±1ìL3gé<˺ªä¥qв8Eq‚šœŸ Ú°0ôÕEæ’TŲbëʲx³4LÏЀ$A.HJÔR-2biš²2[W™©…§,YžÃITF¦ji =ÍÊyVhzÊS òˆXIX²š² S»¢0¶*3fQ^|A‚ZÏ W1ƒô “ˆœfZ¤´D bô« #%?A„W¬œ.¥úC  –ZN\‚>AųdÏ qBÝÔ¬!É;ÜcšYB•Ñ4ìP0c9d“}¡Ì$-n EeÆH¬²(ÑÀ¦fƒ²ƒV° ?Ü ,E‘>7NÁ€ƒbÙ!®+QË5Ki:^x‚šfÉ¢yážI:žIL‘Pý¥´´\"—è!¦øÅH¨ËéA± †UNl ÂHÈ."ûªX!6 ʲ€AÍ… #$E H`·`»@ IP(Ù¡nFÊÛ©Y ½X)- u击+hþz^Ä$&yQýf1A¯Œ K£˜§aŠ(ÞŒPgi”ŸœdEhD-/4ZLâ“<l›YA‹W3à‰2r Z†_œ$"ËÀÉáç™VQ’’ªŠòÔ1ýáwOE¯l†‰Ã]”tÕÓ,&Š!=-š%§yøÁZv@ªž9?þ6ú’de†‘Ðn‘Ž ª²Í…6D­éF^4Ÿ¨å@¢g…1â@S3À5QÃ*JP%(èÙ1Òôh¾”ìgS3 ¢ªLË<›vA–5=Z¨Œ Ò²BãåŒT?Ë$Ò+HçXDVØÊ´HH©Ñ?Tá&!{jØA@ %)š‚xÈzQ@ñ&8p"Ü%t^¤'\¹N¤‡8ñÉ^RFè)³Ùánðú8þ@ š+¢ùËYÁ2&.É‹áÉ sã“}x‘Þìpˆƒ\'KèdQF Iîdß)ügÙ-NIÖñ‚ÊÒ5‰Ú¨¼x1TV©V~‚Ħ¡&AM]Už§ xk˜õâD P7|ü—‰‹scãáB_À,Š”R}üpøáÄŒÀ´xy\4?6škPеRr’E,b)D9?LÌ&H¹ƒ2*ZAQÁï. 7J©&ô:ÙÉF~jŒ° Y.YDW½˜h”’e,³’¢…i…¡¥F네Tƒ0צ.LÔZ”Œ,‹,Í Ï̱éyðŸ±é9@ûV=Z¶,³0QÈWPá÷MÖ2á·Nѱ²-¢89%Ç*–KÒ2áâ•453@Ï 1 Ve¸U‘ôé¨Ñ¢ -Ŧ£dÅòæçh3­\³< zvPyjù±*|KðMšÄa±ŠHv¨£‚áß**–Ÿ’éË vÓ½ Â)ÍSÏ'À·Ê ž# …À ›-§{*™Þ^hSMâ^ø 4ÐGúL"ûN&yOŒô™4)*hÁå?¨?³Ã¦„¸ý9Ò÷nø4ÐÀ%Ÿ4ƒì÷#$IhŽ"Š$é>€g´ÀÉÜðÜðYì°iÌid¿Ÿ¡ÿÀ&$ϱ*‚¡SÉ Oß[åjSã•Dy”3tŠS£É ªð’Qn,ââdq¶…‘Ç­ÊVVe« m¼¥Eј^X–&YUfÞ±<}}Uüš +ûÖæ®,Ù½*kïÀ{ëÁ ÖçÃ%{VgŸÚYqdó<(çÄö²–«š¯¼Ù°å~ó®Ûç¶=jÝûúÖ±3»+Ót”Owjß^=þòâžK‡«ï6nzziOã‘å÷/î}Ò²ïÄÚ¼{‡—Õ,I¼¹£Ôþ¨¦nm~Ë®ªþ{gN¯Ì.ÖF¤ò¼Ë 4Ï_Gõà)‹åÞ=±ºíÂ6ûÃãæÇì.7^©jêºâØyfÞâ4u¾…£Ne‚xž‰½8I²©(f}ž.Ua‘M°"E²£Ì²,]¼4S²¥Â’g¢D³ÝãåAhô@ d«by'룒µ´t*% Ÿ­=S ôkf'YÙZqˆþ?†¨ªBÃþ-¥ÛWfUæê “E™V`ôñmÚðDUHŽ™' ŒW£‰ ¶«Vফ¾2®§„í.áyšt$¹È_*ò“‹|5B¿yà  œ$C¤IÜbU…íá@ ÀD´9\ÒÌx-ŒvRc¨p_³‚`”À-À’’dknœ.<'…“j‰JЉL1ÓLÊÀý[ж¯Í¨=R}öðÂÕIó2EUùêâ4)´àVyxŠT]¬ÎŽ£Vk—[Ôl`žX9%ÍÈŒS«çéÖTZL?£Ønm’’L´¬DŽQ"â8K…î}°Fåçáò¯\ÆL>Ë€8GÊœ¥8GËÝÕg…pô3“<õ«Rä,ãÏQK\c A`Ɉœñ ›úk´Ò«8—¯¸‹™stc™ðKTØdRÀ/á~?¹Lÿƒ¿Ë_<þèëô=9hJdோi®LÿJ’» h6Õm"ÍëWºÏT²ç„P§ÿæCW‹8GÃò‘œ5?褴äZùé:vUAEó凹I"½‰î¿B-#3Ä)Äc*—äA#8¹ÿÊ£øÐs4\Åwz¨ë„Hï©D÷I ÐügòÃ]E‘GÌ šûc¸Z6 $¯)pÉ$8rBÈ>Ó@áù+dA3ƒ^S@†¼p 2ž ‚æüÝOn¨S„ÇdD·‰¼0çH¯)„¹?‚Ló›Añ™&Œp%3hv˜ë„ÇŸ©~³6H*pV¡ø:N€>Ò{&Ék=p.È@2À ¸ö ŒÀBò¨@²Ï,¢ûTЖހ 0Gn˜;ÍßÐ Üô HÅo p 7…ò¡°„Ê„»CP<¤B.Ѐh*hî„P×_æüî3“è ;¾g’ŸC¨ç4à›g„º‚—ÐB“2ÄÐ8ƾ³ ùƒVàÆÁîS )*Ø9Ìk:(”ã%ø;M{Ѓ ÙÁRA†2#ýgC ¥EÎhQ± Ü0W:>»/´×*fh@ÖñÀ| Ñ‰•ÓÔœ`1Ùp+*ØEÅ#É™aj~¤IÊ€XJ¡\…”@)3”èÂ$z†ìFòC˜#Ž „›Â3Ç‚gƒ[ƒmº’  “ê$ á‡C³î9ƒ0—ì;;ÄuJ˜û4¨þs ÈéГ¼g1.péãÀ fp£»ƒ£ˆž!>3Ãæ2I^‘Jˆ‹‚O‚~ “äà qÃAÏ&û iA"jƒàFõsáGøŠÉ3‚è!ÂÓ‘ì%Š ÆƒD ú¹ÍJr…Û‘½BݦB ì4nMpâGxKÈþð>3~ðwü%ÂcÅÏ‘î !€ÇKHõgámrFÇ?QýI~³á¥ —äÍ'ûJèAÌ0wøÐì 1|€78íÀ7ßü=XDw‚Û¯ð»Ãz&Á9Âs:‚ÓDáu€ÿ¼ÏÌ¿Áÿ 4¾³~€¿"èáæ6þ.¿‡ïŠâ3û[ OÏ,Œð¯4ðY †'”â5Oõ÷›E!8ÒBœ£B]”¼°_§_H³!ƒæ°#¼XT‚3›ä  žÿÿPvÐUUÛÞ÷½÷Ýû®^é%„ôÞNNÍ)Éé½÷’ÞRH¨¡+"MÅJ•ÞE” ô„b¡×ôBä|ÿu6æúîõ½ñÞs¬1÷\s­½vÉž¿µöÉž¾‹KG‡ÃgGB•QŒyt‚IÂB A­Aœˆp8hëÑ'+ù1äq£p*t"t“ŒIΔ¡KIÄ9LÓ ÜŽMš Â_ŸÇ7en5×!ccÌÙFqªV€k¤·*i&¨;Ë–âÖqÕÉ„3-  cŒYžøÔ‰¢Í²D—ŽíÑó En…I—¡çã&Ç=Ÿk›úÙ¦3îyTÁˆp€1À¦\¢µ¼p‡<1ÇŒ™0ì£kBÚ¥©Ê|›>0æÛSÆgªUs,\”xƒÇ€F˜ôÁ21W …O˜NRúìr{ištœGŠÍ‰¹:´’¡IE–>³ª"#ì-ÐúIU3 íɘ 8åqe©òÅfÌÞïç’”z$˜dêY('åhJ=2·2á­ñöÙeÖÊ,ÅĤ<]Z‘‰¡ r-I9fÌì"Çg`¦ w«â¡ÀÍ.‹.váT°Òut‡á«ŽÕrÆŒÏà‹)›?™ýñìâ\´Sb–Ì.´Ï(¶š¥QNu°ó,&¤¡JöhäªE% :‡4lR¶ô Óۥƙ…ªwJui9Ò…mÓó¥nîø  LUž%…xŒ‰9nAš•í61ÒÀš¸<;»$-Ù¥ŽÍY+²%' E‘›XK8*C8!WV™#5sm,»"Â, É2Ó¡8”‘i@)y8Øý Öi‰-Îd8mº(s«l‹å’ªrͲws>žŸûÅ{E+—”,›—]à$ÐŽ{/Ã_žÅ¯']þ^Á¦Ï¦­ýpꢪ¼…UÙ©ÚD)s˜SQäNš=Þ´zÉø²L‰Ia‡ë¤¡ñ‘/ÇGÿ=*â/¼¤ÁVs´€7Äi‹QˆGÉ’‡“ÞÐIG; !™ŒœÔ8»!ðŸ)œNSh¶‡>óU›.LÂÂŽE' °h¬Úð«ªBë²Æ91zeH2k›ö/ñM,{Ô+#Œ¡)I#µ’0wŒ8ÉOÁ Ôrà -âtÇÄÖ#82@p#ä5~ì1ÝOÌò× Ãz¾Cè–%ä…¡KJ³bþÈFøvÈY™F!Ÿ”†)›]“¤—ÐòÜJ<„ 6f÷53D·* ‡4-¿Ä£ÊÐ'cccú\ìR©8„Òu¼²4æÑ¨Åã¢È©LÓr³M"Ì"  —’,žÀzE¦¾À./v)²L"LåðCYàPàáVìVã)4Σ)pÊòíò\›óAX J”™FDáqn5ö>9ÏVž®˜c!qÙ,v«Ø@ <—P‹©}iª“ú<«¯òt=F…ýú&þF§‚,û(Ù¡ù6æ¼h…Ú©#NjK<šqn%Ú¢gøÀ‚cÇ£ž8¢t€:jøãHq,è :ŽgfVi˜X…4úA°S#1cír¦½E©šl›8Ý$̲ŠR Éx&#ØáùìÑ ,rº]ÅÁ£À§UÁ‚Å$¥ãIîP'Ù”LØaqiyh O—БN‹ˆ#a'dœP ´ÅÕT ¢²mR<ÿá À€§”‚Ú s â)Z9U,§Š!J+cÚ䀞EJóhÙ&q´jVõºww.›öísv:ë›&]ýæÃó[æWÙ˜æ~4-;ÏÈ«pŠsµŒ ‡0K‘«¦eÊcsñ…ZF±6ÑÂ)ð¬ž3Í“R¤Ž—¯š‘!š™)þp‚cNžrvžzN¡¶ÄÁojÚ8Gʔͷ«~1·,SÎ,1 gfYÞ«È\R•[‘&–¯{·Òµp‚çý©isË,huxóâêM‹—LrOÏ–Î+ÑÏ/3.™èx¯ÂºrvÖ²ÉÎ¥“œ{WÌÚùñäOgf¼?ɵp¢ã“·ò×,ÿùüq€«"WJša‡ÛQZA€I ¼½ÿç‚|9Ø´HCÁ?噢ªbM±‡‚Î¥Àß©ŠB-8Ç£Õ§¸51@2i”IjP1þÎN|%+•ñùÒ¢5Ÿ–O(–,›Ÿµh†£$=i~•uټ̅U¶ªbøÀ\ž™2¥HúÖí’·RNs-œ–¶|QŪLpZª.Î* ‘±†¢ó¹ÌÉsh#ˆ“F=4бp”\êïq%ª•k´Jê§°XÔc³]qrá@«1 35F-®’ sÙ"œ–HZÌ_Dü!y@JÒP‰`¤UcP…Kcø¬!U¨\ì—éfå ³S9Nk¼Z¤D‡þMÀÁe ‘$û+E!úPc8?q¸ n4þjD àœPqbPRÔpyR˜&9ÑMÁ åÓÆH8Aäm»:Á®g»ÃFn¾‘—¥aåè“ìŤˆ ÿ:#xpbà›ø[ã„Ç_託_þRÈðÿ ù·1ƒþ?:)nT¤ÿ+T9ê"Ƽ0zbø`fÔ°p¿¿|ý)Ç?!ô¥„ —¸ñƒãþ8ìVÄë"æè¡ÿ–;”:7!+|7vJvBð ¨1¯``øÛŒô{5!x°„¡KIŒ ÊŽô—0cè¡~Rfœ„Ë ›B‹P hœø#”í—ÂŒLŠsâX1Ar>]šDçóc" ¼„¥ I'äòbô"š˜ òáÇ¥äœh)+R‘#L —²¢õB&È X¥äÒIqBF„"9ýËqFºAÎvèSTBš¤$J„ðé!n4^| !%QÀ¥`G™ÅL}2 |’³"ÁQ¨µHX.%Œ©Ú /FB¶HV)”‹ù ÐË£áRŒÂªT°#Üj.P ´‰†heHf@q*¤C1]¥iÅ×*I²ËñðL‚ò¡øÙ×?“,!ʰw.ôæÑða´ËɢܠÒÌ)49=Ô"L4%'@G)Ž´ŠèJf8DÁC©ãÆ Êç/Œ¥ç‡9d4·2Ñ*Ž3¥Dº• —‚®I Ö$…Âî”'Ú$±©j–C‹GE‡¿UcÆ@‡>h…Út-DGÅtŠâ¥!#E~ƒÁƒ¹£þ¦‹éF0‚^ç„ ²$GóØè#Ò¸~¦¸³2Äna¸Gñðä¶o?šðìÚÞÆS[ZÏíòÞ:ÖþÓ!ÈO?®ÿiß ïõ}¿}÷ñt{ùôŒOfäÏ(0OôÈÊìÉ“Óå9ú8grªäBã‡“Òæ—˜•[ë§§‰&X9oç(–OO_3'gåÌÌM Çmœ?p²aQù Ö,*_½°l^¹³*G ÚùtfѼb×ÊÙå[ß›õÍÇïìøpÆŽ¦ï_³`ÛÒ)Ëg寙;nÓ¢Êý+Þ:µméÞϦýá$Úù¯?>¶aþ/ûV^Ûóùù\1û›&ŸÛµìðÚy;>¨üjÙÔ=Ëg¯]X¾nÑø/Þ)š”)›”¥,´òì²è;×­‰´ä;8%iÉÀ˜tC€Räæ„€4eBе’Ë”BÕø,1ØÆ* ê€s@DPÄô!p®Ì‘–DÉCU²±åX§%º(—ÿÞ;iŸ~PŸÆZ³¬ôÃy“ %ë?¿ôí´¿tf¥YÆíÐÇzltƒ‘›É›;ÛŸÍWË šÐLsÂ8©YlÓ†Ú ¡ á 4WÔô‰òâ|vª-Òi ÏÏ`ÚM‘\‡9ƬËò$¤ ЕC/ŒNŒù;+q€$eTbÜËñ‘SˆÆÈRư^úspbÌ6íM€–ˆ7ÚªsKÒä©¶Y[èØÅ™&þ¤|¿s&¦~ônY¶3…Ÿ8R˜4V+‹6ªâS˜£Rõt»YX^wtÓ¡ÝŸ¯X:ñøþ5^ïí'êÍÎÙøÅìÇ7ŽÔVo:²wyó“°ïÚ°`õ²)?ŸÛÝñèìwÛ>8ðÍ'ÏZ/{»Ù²ê­M+f_=óUícWN﬿yÔÛ~ÊÆå³.ߊNn]Ù‡ÍÖû§`?ñãX  íOµ_ß»~ÀÛyýâ‰m—Nn¿sí‡g­{›/zŸýÖY_wãÒžÓ‡Ö5Ý9æíýÍÛ~¹ñv÷ÉUÔ^9M<Ér¼7úº®Öß;öèΑ›×÷ýzõ»î¶ äC:½×Û϶֟îhª½óÐÃÛÕOŸø>˜ã½åm»†AÞ¾ºß×ÛOÞ§7Ð'ÐzÿDýÍj’M›ÊA !_ø¹~±nÛµK»z;/ú¾Y÷›÷ÙO·~ÝwïÖž'žv]ò¥øD'׺;Ο8º¦åñ)ì×—â ÜhzxââÙíØ;ùiÒç ïÓŸ›¼rî+bĦ÷æ“–s$É‚÷ƹ“Û®]ü–¤]ðÞ&™¼·;ϯ^ÿÓ…=$ÃÓ[}×al}xòû¯–|»mñ£›‡Ÿe¨÷ç{¿ü¸wçÕßÞöè1’3s>Çö-¿ûÓþæ»5¾ Ýèi:wdïç_~2eÇÚ¹×Îîôv‘ìáÇX¹iÅÌÕË&}ö^ÙÞmK[ï}Ú|uÛêwß™š¹êê½Û>ú¹î;QâOø­IiÓË[WÍ;ùãú¶ûµw}’ïL®ÌUãöûpnQÝá¸q÷Ωtâ96¥È°éóY¸ß¾Ý¸8×Î/ÍRà1oVÖ²÷ÊîýâÃE¥eôÄRcQ¶ì‹&øîó «æd{’a,̒Κâž?+ãÈ÷O.QÚµ‘iÜIã”3'çÏHý`nŽI‘ãäºô‰Ù^U™yF…Í­g¥QJ~^YàUêÒLI"¶?3rplÐ13pëÑ haCÈûÖzØ0‹H8€9$.ìÍøð!ÑÁSØ!‰‘#¢%„ˆ žáG 0$&hDؘ×#ýˆÙÁ´7cƒ^ü ´Ð/ùþ`B”¨±¯Eú ˆð00Êÿu¿!ÿ/ÌÿÕQƒþ=hÔK¯ {ý…A¯¼0øÕÆ ý e„Œø‚ÿ°¿B>àL¬ÆúËÀ¿¾àÿæÿ­{qÔk/ +(¨Šû: |3Úÿu„ #_d„ a„ ö"ôØ1¯A§ÆÜ3%~,/f$,Éñ~ü¿èѯ2ƒ‡bf‡i #h/rtRøHa\,˜*À$¬=TΣV`Á|PÃ‹Ö b•ìpÌQ’ã0‘$ïå“ã‚87ŠSƲŠD‰61]NÉPñ3´‡ˆ¡çÅ(h!eRIšg–äêE©*®5™†VA‚ °$IÔó#\r¦]šhÉÔsM)Ñ ™ÊL-èØBÙF~©SFÞ”)i&Q”ESèe›y> ¥i˜.%#CÏÍ2ò º]‘ˆ(€ÎgfÙK´¢J³¤HÍ)Ò'åë’Tì`0’YUlâ¨hŽŸ‘6t²'9M=#[µ|FæT;wq¡:ƒï?ÙÊy¯Ô4;WU¨e”›Ø©‚À·Ò…³<<±vÕ´Ã?,÷Ŭ›}]TZê_ãön_|ôû/È÷åH~òŸ¾§žõžß¿÷ý=»æ?í¬õö^$™šÉG~Î÷´ž:wbÍ™ã«z:Îx½×H°ëAy³·ùü¥“[!äs»ˆ,Ý×É—@º¯wÖŸýùÜ×çk6Ý»Nâ8‰¤ä³{×Ú¸prÓÏvuÔŸ"1´÷§¾Ž‹Þ®+=-uoj¸SM6Iæeìô’÷ImOóéÎÆš®Æ“ÏÚÏKß•gmµ$«QIl„Zb'¹.’¯;v‘o<¢¶¯½ŽXº.öø2M÷uœ}Úz¥ïƒ{I?uOÛkIÞFr¢.‘uÖõ´žyÒXó¬£Î—ÒÑ—ä‘|æèù¢Q·Oé¹@œ©$$ßkíSì½ëÜs¡>`Ÿ4Þ;@2•SMºÎÁ“d0ïw£<1ªC’–º?)áÖåK1ÙuÖ—V’쫤£=ûìCÂ5¥ÓÛvº‹d&­{öätoûɧ§|»¨Eç”þìϤ§ãÔŸ Z¡4DW褷ž:ì:Êu’¬õD_+®à)oÛru:jqÅ{›N«ÖæûzÌ“:R¶Ÿ%W¹gò¼÷Îm]/zh;ÑÝ~û%'¤§®¯»c{Úy† ª¨ñôÀ­õ8våù1>¥Îm-(èÐ{‰|š†\\Ü—‰@Áf×y*?»·ï*ÊØí»¦Þ«äª‘«sîù½A}놺šÿ*O²—þ‹ô]&Òs¾¯ûܳ.\èódÓ{ž}ä‹£gŸ=ñFè^ßîPå½ô¤é(9 Ü9¾{±³é8îÛ¦û[Žâú¶7Õ4ÜýñÆõ¯¯ÛzáìÆ_.ílm¨¹óóžóg6@¯=¹öøÁ/ný²rìÐç‡ö/;qdÅÉ£+¯_Þyû×ïjO¬ûn×â½_¿wúØš{7ö?¼}àRí–šC+^UýÃòk¾¾yuï‰ê/Á“_o[´gû»¶,>uxÝ·;–|ùÙ4ÛÆ•³aùnÇ’ÝÛ?X·bÖG‹Ë–,·êÓªõ+g£öã÷Ç£õAY»|&ìÌ/ž;#cþœ¸ÚóÅ'‹+«ÊíúÒõÄbÓŒJ×”R«M“è1q:挓K,…é2³2Þ©gzÈg+h“ó i:†Ya`éa©Zz±["cú <šDURP®•WUd6 Ó"^Ó†ëÁ6i”I¦dIŽÌ 6À*‰Ôò_‚À»Ž”3$ÆïoÁo¾ç÷¢„6ZÎð§û¿<úÅ4?AÈ@~ð’¨aIþ¯F¼ö}ØjiþÜ7âGý-ôõƾòBð€¢†þGâè—c_‰xóß¼òÚ q#ÿ“î÷Jìˆ¿Æ ýkÜÈ£G¼$¦¥ xãfЛ‚¨‘ô€7„±~)QÃ9Á…Ñ#ØAo$…¼!‰É zú7æè€¿°"^Õ”ܱr–ŸY‰ãÒ p&Y¼Q§àG ‚eUÉQà+}R„Ьb*Çšø‘6 Y&R3ƒL¼0+?ÄÄÈ×2U´‘ü…z¶:fˆ›Xafʣǩb礋+LÃ{ņIvî†yEŸLJµ±Çš˜cí‚H#7TÍ›*OWÒQfªéqŒ`dº„QP2ä ΔHÃßœ¬¥1°ÇºEѲ˜aÐá JÅ zUAå‘Åtì 3?Þ¡ŽäèERöp“4$ÃÊH5ÄãœëÁã<¢|ü:*tqìÊP£x¬F0Â'£ "•4 â±sâ 2"Õ”“&¨,R~0/wõÇ]dáìÔÍ«ª&—©êÐQy¡få'S'—›Ó¬làV\àæN+UO+Qä9ã@;+ÞÏœ[)5Š+…ƒ£ÿC{)ÕŸÎv#ÍêP™`„J4è"â ׈ ÒSR- 4Üc¦§Y´bàM¶“‘å 'Å¿J ÿkŽ‹iÓ„«RFdhŒB0RÎá4FrLšp’Ë՚ݠ¶ªÃ?^T`S†%&i„5„óªš7Ê®K×ÇèÙà ±³ åÓx–¿KBª*Â* HÕF˘CÈÏŸ²DJÞè|WÒŒ “ÇLsÛiC¤Ùœ ÓƪdF-ͨ¥«¤qQÁÕ’N¢ŸYË G‘ðƒ®Ô}ƒI1 Á}ƒ“íä[UµOOu?>îÅ\»ñ”·çÊÓ¦Ó7¯í¢bOçéŽöã-MǺžœñ}\—<Ï›>¸»ßûôBWÂÁi<];Ž‘5z.äí³ôvž cöQ—ïôú–äIÁ©ðŸô6W÷’Ô´d`$›/',$IéèK,‹‘P9ì0x/®E+IÂBò°àìµ£Œ*Ôác§\Ç~#:i8TîÜ*3uãQ×F’Ìî÷;á½ÁBŽº÷Œ·´‰›–;9 }<üáÔ*ªÖ‹iÅEø“kôô,i}ÎÔ-Atßå#µÏÎù¸ÿâ“뾉Ãü“¹e„þôêó¬¬D®cçòˆ tÔR­|‡ç­| <(úHj×ßžµ_êjªëm½@•˜Öõu\†ÞÓr¾åÁ‰Ö‡'± ½ùþñ®†Ú–[GÛnáæ¹Ú~»¦þúŽ;Ç]ûX:®µÞ< ½áçƒO×z[.=º¶ïV펗v7_ßßúÛ”/{·î+Ïÿ°âÞ…¯ïÔî¼ppÕÅ«.^sµz-ì—¯=¹wùѯ–ÚóÅåÃ~:º¹nߪcÛ?<¸~ÑkÔlYzn÷ò[–îú°jûâ‰{>õݪy;>µþýIkV|¹¨rã’)[—MßþÉÌ9Åæù®e3ó`_ùnÙ‚J÷x·8SK/OS¼;9cÁ”œ%¶\3¿Ð.,qK« ©ÊÈ8GÊäluUŽ¦Ô‘bMS1G™ÄÁÅi<—:ZH„¨­ãȇ«˜~à7-7ˆú™¨„è6$Ùõl‹’!g‡¨t\Â9Z;Ø,ˆ²$GjÙA:v€ž¨eѳü-ÜÙši©ÒõóÆ}½tÊç“=›ß)ܽdÂÚYÙ?~>cY…}ª÷~©u~‘ɇR%ø)âFiY²øQÊÄ1†¤ 3@;B7›!(0B° 4²&GÚR¢¢€ ”l-;M‘è’Äe¨ÐL&>,Сdª™9:*]7Þ)œà‘”Úb3·ÄʯÊRUºÅeöäIi²©™J8T¸DÓs43²U Km³sU ŠMt/*6½­š—§W ƒTeH§ç(ªò•%.^ž]–!¬Ì‘NÈ–¦kbUœQ "'WÇ÷·HÉÂEežl|Ž$ÇÎJ7Ñrœœt ݦŽrhúä»<Æ­NðŒKdY”ÜájþpƒhTš!"ËçT‡uÜ6v¦G ’†ÈE],„O´äݼ/–”è%IÃfN0~ðNfQ– (G4<Û“l×%èe2®?vd‘‡)y#Õü‘ ¦Y¶.¯Xò–µ85vûêŠiòLW¼ˆ;0'51/eRM'¯,–UÉ 3ùjQ€„;2ÝÆê(’ÇšUQ.CŒÛëÔGƒ[ò<ƒ,ˆý÷ÂtžM¡LmR„ PdQ…a`æ›:Y ù¦„7œ›ø:'a€U†3* zÑØBOÒä"n¿b·"[„2ßÎpËCÓ¡¥vF…›“oŽW³+™C´I#$ôA ö0«,Ì( =§ ȇ‰L C”Jh5%ØÌtJ¥•Ç™µ,·59™d7rË‹L¡c^4©šœn|p¼£áøãÛûÉÌ®³îɃjHý¯{{êk:éª?æí¹HMÌ[>i;ÖÚtøÞíoÞ߃(ÐðxÊÖæƒPÚZµ4¨ô="…×{£éñ~ؽ}µmõ?6Þû®éÎ/øð耷ýijúC¾\6TZ®#݇:ëøƒÇzZŽcÞý´ítWÓq•Qšâ±ÞÖSdyäI-Œ° pÕ?Åno@”9NM½[!襖P #¦´°7=øñYû‰¾Ž“Þ'§ Pž¶ïn>ÚÙX ;IìÕrŒ$üj&©¾° J§|PER¶µÖJrýÞª£þЫ¨†TÛ~ ÜH*±ÖÒUãADL*´õø¢gÐ¥þG*œ!ÒAú°bD¶¤~v”?z@“çáëhKÅÍ•þ¶M‡¨M”ؤöø’Í·ëþÐLm¢’@Í—–ð¢mÓ¡ÿ.pÿwòÇž©óпSj“:ä~jž˜‡œ·_«V2Èž&rȸß0ʇÀa ¾¸i}I‡}Áº÷4¥Hh?JÀWöü~z©>Ñ?9o‡pþ»ÈuÉP pÑ?¡ o÷Öû’ÕúJòÿ&’–0Oc5ÉC×âËR×t„JD‹Aþ«àäO¥?û-•»Ò]ò|¿¾D·ýYw}Ù~ü±Š2’»¿;±ùÒòzŸàÏä$•i—ê“ÊçKíÑÛ[K9Cï|ô#åÐxswëݽÝÕñ`?Œ®ïxøóÎæÛ{ Pýt=>;J*¥¯›õ5]jž<8Š’ä‹i%ßjîk>C¦]­¾Ü.¶:’hÆ—t¦_(OoËY_öçÓ$?KËY(¤Êgì©?ÑqÿHÛÝíwõ6œ„j‰Òr–$ši8‰±ß¦›?¶Ü>OH׃c=÷y}Ÿ}î{t¢÷þ±g£|ú æñ•oë¯î¹{nçíÚíЛއ͇—¾ÒöÛO~;€²õú¾¦ëߣlùeÛÏû»îT?»w¬ûî‘î[‡;nì¼q]uÞ>ÜqëPû âO5ìºy¨÷Vu÷CÝ¿ì»Yí½[ã½y¤ëÚ~ÈÓßyëÏzžì½s´>·ª{n¡äÙ½š¾ûÇQ¢êéÝc®›‡›~Ú÷àê_N’2æò±u¿þùéä¦Ó{?½vlÝͳÛ\úæÆ™­WŽ|yçüöÆŸ¿»snçõãv¯šõÑôÔOf¤:#ké$÷ŠÙ;—ÍXõvé¢ Ï§sƽ?5gÑÔì _/ˆÔñÂ%´1jf5% ¨£f(h~☡jÆEüp“¬º˜Ø®”°"gvŽ&59,GUid»Ø~–„!Ù)Á.Ö¨ ~@Ž0$C„ª" ÝÈð—E µpÃ<.IŒ-%Â)Ž6ñBTt?·46ߘäEQ‚Í, Ã#‹ƒä8éJ,h’¦H 0£¤åèX¹zvª<¾Ø*(w‰¡d¨KÂB3¯ÀÄE“{¼=¥ÔÂÏQ%BòµÌ\5=[I+@C5½Ü–<5]QáÂa’G:=KIÏô4ɯPŸ/…¤¥„.*1OË”M𤔺ù¹&:yK¥ŽF8N×Åcœq*"M h»""ÛÆ,Jå;5Ñ:a€U©H“F:uñNeœU•®gØäáe¤G¥MíÑEhÃrlqãÒ8.M˜V)Û±„xÓe¡Ë…SËõÛÖΙ7Ý™öWUŠß¢9iKæeËIÑÈ‚œJQHª3¾@=½ÒV‘¯žQaÑ¥ éo:Õáe™I©†Ðw§hçWi'ñóÓ×|^¶ðmgº#6ÍWQ$žR®¶h¦Ž×:1:i°\àÇ¥ JJxSÊó`ä¸Xä_ã ¤ÀÐN¦ÎO|lÔ‘óGBшüò`0X sþ„ý’IjÑ„ëåA¹©xf;™ îˆ;£,+¥$C0µH9¥PQèâäZKœìLUx–:bÑd˼ ½Gbà6§økù£]*òÁ[1}H›ûn•«$#Åmˆ×(‚ÔÊ ³!Æ ¶šm&–ZcÒ0SíB>+€7‚=˜0lù²*ß+­˜5Þ;ÐÙˆà{ª·ä©lÿtë©?Úv÷ǧÍÀ² Ñ\ÿIÉÚ~´¯ëxgˡގ£Mº;Ž6×ïoƒÒyÌë=÷øáwìéì8ò¤½º¹ùÇ––=]5˜è!.Ôiúe7I=Ó|œ0Ïãj<çûšŽê<ÓKæ¡ “0!¦£(ŸµÔPDÁ³4ROHƒ*áC(¥ýµÊA-tP:&°dþØuSQlböJM„ øH½æé8E¦‰0‰àÔZó´™°MO#è°ôµ’3€²_§<áƒ:z ZQã„N² ’w+¤–Œ¼x{OI†áóDL$ñú÷Mâµ/\¢¤B'93­G© NBmÏ2Gn'A¿?”Sáõ­úKÔR=üS‰¶„"ZŸ‡oBSí5ÔHú÷õ¿,ûûü' !«F<ÔQ`„žÿkÿ$Ü·N¡ Á_ÿ³¾æ»æM6|»aæÎÕS Td°ß›f^8Å0o‚²baÖÙ–ürrMÝÞÇ;86®_®*ÚÍkaŒ(EÍJ•,,2OuŠJuœ*·,[ž0ÎĵòBÙZ¶Kgâ…é9nˆ3%Ò‘aä¤KcÝÂð,ù§*'?¤ÜšTåihoe)'ÙøKJÍogʦ9xWØ>©´C ¤³R…¡¡ØÅ+ró²- wTŽ•jˆ7ICÒ̉6u”9TÌ)eŒ2‹#&䨲ÌôtCBe®(×N/Ï⛥cò %éIY–¸$Æ0vâ§…Y”+5¨£D¼ÑŒ¸×õ²…s2ÞžjsbÆe%¯þ¸âý¹nsœYå4'–(Jòåã²eiV¶A™ag«S™Ñ׋ü SYBÆpHž3zÁLc¶+ÁeŠÒËÇæxk¿¨\<7U”4ÀSš's‡±b0¢_0kÄ ðOº•&ãç8õÑjá›&`Êy9™ñ&œ“å`äy8:I€]©• “”¤¡:PÇaˆÎv³Ð¤(ƒo–;ÔVEhº)~|–°È”iJ(r±uÜ‘nyp®1v÷êé_ÌË~w¢qþD˦e•S äfq`š>Þ÷_faÓÆiÁ<…i‚4=?[îfËEVÍcOóƒ¢HƒŠfÒеò8irXq®zÛ†EÎ~uïÆAòÚèÙå–Z$?Aiø>`!kìä‰í‹2]ÇÉø̸«½`‰Ö#ÞžSOÛz{N‚|îíh9äõÖy½º»k?Þ àéh«öz/7ÝÞKr‰vž!?Hð^î~TM¾ŽÞ{±ëñ¡ÎÇÕ­4?>ô¤íذGçI_ìÆì益š›c§M‡‰³u„iÊþä)Û0CD¬Ûøè¦ÆKÛš®no¾‚ë¸ zý¥-Ïo¦,/n†¥õúW(oYÓøËWÍ·vŸØ·xó%¥‰ËßËX»,ÿ³…©9¬çÚÍ0Ì©”-˜¦[ÿIáw›§­Z”Y½inž2"[2/_1;UX®M˜já|>Áµbrê'®5Ós¾[6mI¹ó“)^¥=`Sbåƒ<€”ˆ"k²[<5M<Å•2Õ-L°%(cÞ/3/.1fŠB«\ÂyyÚ/¦¤xæfJjçx’×ÎÈ€¾|jê[ò\yœ>ÑOO÷Ï×%™“Bmü°tY¼[m„§Jb)Ý#Žq £  Ð’&ƒ¥Ä"ÈTÐ ôœ<-+[E‡›¨Ê'À’£fÀˆÚ‰ù5µO±Ø˜4ÎÄ™ä—Ûø²Œz–rjº¬@Ç(Ô3KÌIãíTÁ^jáVeȧeH+ì$ÍŽnºG<ÞÈ.VÓJ Ì\yL¡ž^dbfhéšX§* Bñ˜è.yD–1±Ä#(°srÌŒ\«ÐÅ-psÇ¥'ÛU‘ò¤‘iˆY®N«‡:“KÓ$3JÌ™¦DOEŽ0ÃWœÊžY®œQ¦˜V¢˜\(‘¦*%¡}Bªƒœ4’ÏÆID‹xɬŠT€‚QÀŒŠÉ”r͸l‘V¦–†€‹ä‚€¤„ÁÉÌ3³8C”çæ²c_QF~ônúÞmsæMÓ}²(íéæ9SMÌÿ¶·§Y¸‰¯9 QEY‚4M- àÄ¿žé`M(V©„c¥¼Q FÔKØ]iŽÐ¢ nåÁ<ÚëbÎPUа›ZñXnÂkP¬Ú¦’Œ% U¤Œÿ¸L±pAœcS†xJ3“«ŠU¹6Fª.&ßÎ0%ídh"6~X¶t–û½iöYãÔžÅÓÝY¦`!èÑ$ó{øó¦¹+‹åS'Ê åäNòH§…%ë•¿D“†–—¡˜Xf…¤;“Õ²·}öøZOwsMóý ÉC¾±šÊR‡ ø¬éðÓæƒà§íÕÍö‚šoï!ëðMGH¦Ý¦ê¦[ß’èÓ{Æë½Ø×y¢ùá¾.@Eçñ'íG¼ÏÎxŸÖÞ¹¼ãÞ•Þ¾«w\=²òÞùm.î ¿×m¨i¹µÏK~[ÛÙzüi÷Ù®Ž“O(ži?ò´ñ@wÃ=õ?ô6ð¶ñ>9ö¬éà³–C}͇Pz[À?£GˆÞw¤»ñ`oÛaÂ`ݾŊޓ°ô´zF&ò‡ûÈ,¡’Š_5„èZ|YiÀ$í§–P–?–-> jDÌ%Yƒ Ÿ4í®?L€Ðgá )D¤ì”',T-I|Óvâyÿ­>¸j%DDõLí'“zÁAزù÷ú‡’Äßµúbz3a€nLØHÊcbÁmɺ/wO;5÷îÿ´Á·Úð/%ÕŠÚûóþ›>ש=þïJ2†V_po%‘: ᜠúô½Oñý¦•ÐÑ©þSùgÇN.VÓ‘ç/}Újž{Rç,›øÆ@õï:õn«ÿÍ×?-Ô{Ã÷#œçï¿ZHªh!ýk)ÃPë-”üÓŠJÇ)r›| óøa•†jÐ ‘–ª ”Òyç{@¥í—oÀ$–ë» ”#ô&ø&3ø„ <(¡,ˆÑ*|úºM$2žÝð vÃÃ3ëïŸ]„ц‹[Q§áÜæú [`G„…:ei¾¸ q%e¡¶âþÉ5ά«¯ÝÐþ9»áÎ|ÍòG?»ybùÝ3«ï]CÉýZá:ȃºµ°ß:¹åã ê/n|pn]ûÍo¯œøb늲ṳüèÇû³Í¿ëð|0ÇòÁlë¬ñ’¥âEÓ §ÊÒ—N6-(V.­4~:É öY9Å=ÃÁý¼ÂúY¹ma®êÃÓÖw‹çç©WÏȪ´°èmì±ÅzV%˜GG/3²@8&N±&¾X;Å–4N[¬ŒÉ—„¯˜ê^Yå™`bNs Æ›˜KJÍ òÕ òTŸM´¿_¨ð,ÈQb³ÊÁ/PÄ™~`žLYX$SjM.2r¡çj˜`GrŒ XÀ<@ÀX¨ÐÈ1q‚@AQþð&ýxP¢j‚[š­N¬t¦ä¨À3 KR€[þÉÓ$zD(‹llx äªiùZzUºÀƒŸ`ãM¶ 0ÚLax‘†–. +62KmIYúŽQD^]9”‘Æ”@5{dª&6Ó@³JBò»"Â" -Ï[á2Îw”‚ï§Œ1ÉÃ\ú„b·0gU­L…h>!O\äá¼3ÙÚ™—\‘“<¥Hª‡xx¬2a‡>X)’&ûËø£5â@0FIvJš%Á¡‹R ý ŠàÒ\‰ˆGþË;™3‚|Ð3ZÈ¥L ÊtpH|Æ@¥pt®‡ÎI³Fêe£ÄÜÁÀ§1Ú¢ C)ä –ð†-˜ãq›ã*Šd3'™R­ Y.fa&_#›éda3m|Èñvë~ð=Ê®GûºöµÞû¶·åÇ–ûßv×ðvÖöÜ«î¾ ä8wåèê|kBI*çë/gx;/lú|‚G±x¶ëçºÍM÷@î]ýzZŽh뒲߯ٹt|¡&ºX»|fÚ•ï?ûúÓIG¶-hüi·½îÉ£#5Þž õˆ¿‡Á6ÞÆƒ„jšŸë(Ÿ>þÁÛŠÈr°ýÞž'w÷{Âb·j}TƒhÛE˜­«á€om¡Ú·2Pã[C¨ù}þ²“ÞÖ“ÿ(Ž’÷kM>Îi¡ÈÄgoµÓÆj¢ÿñeÊÿFú:À!MGžªqƒê™€ŠïµÎ?½¬ù¯ýcÿײµæ¿¼¢º¢^ QÆÖÉO”}Ë>¾ßð´{þ3fßæÔo˜q–·Z|ó\Ï`|F ŒÏ„ô=8ðôîþÞ;ûºoíí¼±§ã×ÝÔê µpAÁ ”Û§¿¼qbÊûu Îm|x~S¿`“2Rr¯v=YX¨]ÂÂ-§× P° #PáO—ÄÜsÿ(ë|lsv#jÛ.!âï ëˆÎ@;àJdÒ|~+µ°@ùÀÞzq;áŸ%tôFµ…Ž¶è¹¡vJèd]ÂW %üai¿¼=Sc€…ª%ÌS³œCùc/mWv¢Ä^îŸø’Ò™ hûè4á¨'×B‡­ÐzûãháOØ©våž8K°Pã$礎P,ð¿S³²áü†Çg×Þ;µªõÒ–ŽŸv4_ØÔxa#ôGµkÏmh¾´µÐ[.n†±þô— §¿|P³¢éì:ÈÃã+[ê6´_ØÜ\»:,—·µßé¹¶,„óŒ½PçGÔžŸ\ÝE®Ú¹-ÔÙ€u¼Í·’ñÔ­CIÆsu{Ûå­Rs݆Fì±v}+:¿°¹ýâ–®«;žþ¶ûÁ™5j×<®ûòðìjHÃùuÍ—6ÂxïÔŠ»'—ÃBUÝ?½òÎéUµ{ß_·´pþ$ÝGsœó' ³K3Çɦ—ÈæNÔ-™í™?Õ4³\ýöD⎕‹ògIWh?Ÿáx¿\óõ’²šµo­˜âX=Í &Y:NÿÉxËŽ…ãÞ/Ñíÿ¼êƒrã‚"m¦(A¿P_f W¹“3ÙÂÐ A`¦ p†‹7NY$ ÏIü¨LÿñxãOòÜ,™›3f–[87SZeMZR¬[˜-{+M°º*u~®òÝ\Õ$?C™)ËÆ§Šb€.v±™›&‹u‰"3ñ ªt #²T4(©ÒXPkJ ¤~„ )4rJm‚rG šçjã,<:Al¢„Êñv“R¨eÌÌR·ò!SS¥z†$ºÌÌ…阕öäI.Q‰‘“¯Nœ‘%Ÿ•#Ÿìä—›˜¬œr#>Õ\¨Ž›œ–2)=%ßBÏÔÅÚä¡áX«,DËã”…9¹`§"²ÀÎÆ/ *#DŒ!.mÌ„|y¦éÐÆßŸIîr»<Æ$ 7‰ƒÇg‰KÒynmdenJI:R–ÉóH’„¼1ÉI£mƱ`ŒYc3ħYé… pàA#òÏq±¬ê0o8@ˆÿš»ãíðéÃä‚j©‡÷†8i„]¤ç0bÿª“û¥°É#bBþF0JäW˜É›\¦‚›Q2¾PšífYµºÃ]œL>×c¦oÔ¢à øÀ“nc@`ɰ3íº„Kݧ9è ±t{"hG+ 0ʃsÝìlÝ$ óXä!¼˜W£ý^0›¥ öPOš&¼,•óÝúÙŸÍËÊÒÇ.žê˜Z¨ÈGç²pgJ—2Š5 1ø?ßî)Ë“.œ•>c‚Ù¡·ëâ̪(ƒ2:ÓÅ·èÌ„Á qèGï¯þquÍáu‡ö¯pYhÙiìC{—4ßÿ±÷î3€œ¾æÏö{Û?­ßçí<Öƒ²A¤š„’î‹Þ'znx.5\þvñDëçoeýZ³ÁÛxvÛ² š¨…ŠE“mexš*bVbå쌙©‚éNîW‹KOmœdõœ£kÞ:¸ræîe·,·t²mV,Ë=oŠùñ/ß‘ß$#|×ì{|ÐÛä Í”Þ|¤±»ó”/±/ŒvŸñvŸõ`ãzÏ“¼ÃO΢xzÊ5L,­>&i®y.T†b‚1§¼gˆ´Ÿ&›ÀðLÃQRà9ñÜ”„ŽNôÈ&|È¿zÕy»ÏyŸœ}(UT?ÍÇŸ÷Óâƒ+l6ÎéßW_ýo=¡#X›|ë€&*´Rxà[O ô6_ˆ§~wÔ|ì>m¾õ‡´ŸügK³oÉ¢íÄsû?•ÍǨ×(d}ƒê“DùjÒ–Æÿºl …|ÀÓxäù+j ¾W9>æùCÿýÇû¿,ÿ鈨ß_už&+-”Å÷[¬èýË2ý¤Ôÿ’Q˜Gá%éÿÑ!o}5‘‡3Ý·¿ïÜÙ×L-¹\ÝÑ@½€¸¸$\¹{zí½3B¨jeã6ææÀ’ $º¡¤VZ wüü åI­¥P+-ˆÅ¨¥Ö^@ÔZ e¡‚&áô3ÎÈÞzžBãÙ$¶þkéC ªü#öP`@ép#Ã;»±oú±¤ ”Nàm%À~ÀÀ¨>©n]qïør µ›:®íêùù[€â©ÐþÉBÓ©uh‹ý–;MNW?äPPD™! ɨ=R”9z€Þé êX¨£îU?ªQ8‡~ “eœÓkŸ\ßÙvy;È¡ñAÇg×7]ÜÔ~e §éüfª{CÝú þü&ß:Ï6èà”Ï~ù¦בµÐH(®v}sݦÎ+;a!'ä"9d‚…¾‹Õqå«ç/§|o©æ#ÇøèÿóõÞqQeÛ¾ïyÜûî9÷ž{ö>gwжµUL(É ª *Gª((Šœs‰" `ÀœArN¦ÖnµÛDÎ`ê6‹(LöÞ¾ß\³wï>û¾Ïg|ÆgÌ1Çœk®UÔšß™ üå9 ÊB{@Yï®wþt³õ/Œ5Òë¢~Â]Cõ`!pÚLš=Z‡[€À³0ÝòöF;Úÿ¨ÿ(ôüXýÜðqÂoS­Ý‡RªrU¥)b “uN9‘œ›4;sýëv'WäøíÞ¼·$¬<Ç/;Æ'5Œ½wKhmeD¶Î±8’UåU¿Y7ÑUùíô¦ÍADzU»âù‡3|»¶Eo‹á~W½éP–r_š K_﵉ÁÎQ«´·œ8òOÏ-Mþèbÿ%€ÇÃy™T`âÍZÅr^¥s ñ³÷v]ÊvúÂÓa /ÇE%›TÞnË=ì{»­z­õpüŠíjàf¿L!´ÐÈm5 ëäX¾Bd"å¨,ùœåöVÿÛqãä|C–ã"¶ËŽë—¶fÿjeü/ œFÿC§ÚìÌrüÜÊøŸÅÜU*±‘›Ý§l·¯øìµ )®‡Jj‰Æð¼Ö¤'Šœu ,¶Û dÁÉq_éîø¥—ë2™`½€³ œC¶@³ ” ى‚¤ϱ)$3Æ;9Œ…GDZ[Äw^J~&Èñ ±ÛÒ‚D~Y¦<%Ä5#‚•î™áík+tüÒßÛ(Ra˵^¥´ËŽðæ;.ËŠå 3¢} Ó•Ñ®Jq°ÚNÁ7 öw’ø˜€í–h”öY©¾eÅQ'ºvØ’ïsºs+s|ôÕƒS%go¯ýuþ܇·}zê/¯ÎAÞ=éýðú"sÒj”˜íûéö·Þ_?س/óùh÷‡_n]m.O’ÛìÍô;¸I›¦rØî}4/h¼cG[ITE(§nSà¹Ýéç÷e?ƒ!@¢À,Iº1Õ×6Sç¢ó^·%Eôâöiò3)@‚߯>¼éÿíÉ·^^!'­^]Åàú·Gç~ýæ/³d{ú&ÒS£“],Zþ&(þ–þîÊ臅ߓ0ž_ýë<ú÷>Œ"€¹ï~{‚:¿ýéṟî}ýöÞ™×·O@ÿòàõ¼»^ï›?Ï]|óÃIØð¼úáÄ­Þ×wN½ýñÔ‹[=/otÏßèzq½ 6<¨aáÇ“È}2Õö|¦ñÈ…ççûgŸßìFÙ7wO¿¿{þgSí³“­ÈEm¯o÷¼úYãø±÷í'ÞÜ=ùîÎÉ·÷Nýùñ7ïœy÷Ô/Ï‘;}yù¯Oο»NÁ\2ñì;<Ø??:K5úkhD’ܹ dCÈ£sä‰=½H#Qž!ÍeÖb.NxÁ°Ö³^CíÿªŸ33L=d¢cî<4lò½aÀò9ƒ „|Mјÿü¤ 1×¢K{[Ôc¸ë ©š©Ÿa0&òù%fÍèûß[Kýˆ|Föºü|ÿ Y3zx–ÞÑï×zzñ§{§‘õËý3Ð?ß#‹ÌàÃbV‘È ³#e–™]¡S.d»ËD˳IB#dúe¸þáP-Yo"«0×;^ÍÝ,OÇšà$œÀtñèpIwÆÌ<ÐÎ;ºZtý´;F¯‡ÎÝ=Í |l¿¿Þ œ@ dhˆYå"“oÆ[ߌý½^A?Ûö~²ã-úÖÑ–W£ÍðÃFq4L¿¤E³è¢4™`™&Ì@玠A€«ûWŽB¿½Þ…Rû¿¾Ñ…¡‹_xˆDY4h¢ À¦_ꢞëüxQŒõÓV¡B‰ýÇQ'?øquù¸í¬™ÌOõgø8†ëÕÎ Õ=¨ypõèƒþjxà§¹Ïq³L$ÕÏ'AJ@Ç–g# ÔƒÚP–Ús£ 4†La]'›¹;r_OÇh;›ød&©&™\D'Ÿïë©6 ˜„€.=Lx‰Ì>P™˜_oö,à¦@z#W@AtQŒ¤¦gµOûÓ\$¡áÏ<í¯ë«YkAÖ³:TBèh²@2=¸r×ÂÕßÌt¼½Þùþf÷‡' ï]>Œ»{2X»0…¿=0$ NµÐïntMÛµ#C¶+G}¸8tw¾vkšühYÔ¡ÒˆšmqÙþÉA¬}¥ÑÙ1¸VNœ4;V²¿$²$A°;Kžâk¹=‘ß\üäJõ@Cá©1—ŽdžÝÐTØwQù¬‹TÛùóLøö_Š]WxÈ~o#Nbêk«àÊØk•Þë%ìµj¡)ºiØrÍgÿ¤d'q´"s ß$Bm£“šé¤&É¡ÎùI>鑬0_K7Çet†ÇÖòO.ö_øx®–òy+#]BÔ6Ö –ýçáþö‰I°Ê ¬bkúoÎVŸùI,U+¾—!€'På ´çy®“ñÍœl¹;~!ôY#­×ùÛ¸Ú¶zé?yÊ üéc¹¹§ÓâPÛ„­Ò‚ëö¥Àk…œ¿ÎÛ}©»ýgÞ¬•hðF)6WˆÌ@8 Ÿ@µ½ŸÜ æARÈ5D gVe¾Bý¾"#®ÛR¾çŠ@…¥Üg]l ³¿È$Le£“Y¨}Ö‡È7FùÙ§„y¤†³ÔÞk´Â 2ÖrO«—z|¥ðZ%õXáï½>Dl¾» øpY4hGË3ÉeŸª-®Ø 6Oйƒyäœux¶¡×j€Ÿ\hªUÛKø¦|foO°–•+ÚQäR^¤}tëÔ‡_Ƙ=ßýüäÔŸŸžþðêÛ??;õáÕ¹¯¾ùíù¹ ^ßé½Ò^rb_úýËu#Ý;g¾>p(ÇÿôþÌ{ª_ µÿxöð@cÙ|_ËÛ‘îgWšž|_=w©áU_ëãó5¾>2ÖX>\W:¹ñû#ùø"l‹ìM÷=PÏï9”ùa¾ïõg^\ïùóãó¿ÜûT@6"^ïz9CXâÙDÇS¼Q';ç§;ÈîDŒ­fzü|º÷ÙL¯^Ï_?ñbæÄó'ç§zžN÷@?™ìšïœè|:Ñ…äütj˜ŸDÇ×öt¼õÉxËÜXËìXóÃÁúÇ2ž'£ÍðÀF$4Ó†.¯·ÿ`¨þÁ@ÝýÁ:h؆4Bß¾rôîµxîõˆ·úXËÝþãw®V#rv¤ øáyŒ±êdÛ½þê»ýÕÐ÷jîÔP}ð8úYtÍdÕ/ùé6:ÏûñH=:kôÎóÌÊ<ÝSÊìPc¶ƒ>aö—>cvE>m ‘d#+ÝËŠäªŒ7ÁF$';ZéÔÿP¿¸ÞAvŸ2ñ´žg̦ÖWÌ×9fæ fó*ê'2'[hÍ´ý/˜Í*¯™­,o˜­,Ô&ûcotþt÷ÙîÂìr¢¼£Ûhov¡‡ëAFÐ'pŸY3ºÓwŒ>7²›…Y‚ÐÞö9³¹‚tdc¤Û…€C`P\aœdu‚>ýTF`Cà$Ý7ÙûAúw-¿/šŒàAG\þ–„ ·¢£{:ñø!ë>C ttOyFo“Yf2‡LÎ0Åi”pþ«¼nçømy9Ôøb°"ź÷•¶,Aæ†ëi›q#d‰±é­!æáÀqxÀ9°A#onvÓû¥‘´½¡;…¨Pþášl¥Ï^N_•¾6ZŠF’}D£´…hêÝ«G€4 à¨ãþy2B°ÌCàg”À a¡±F$%{`¨À¦Éy®Í4Im8éÕŸO0îT'iä8™U{6Öª×ð côãÁf¯‰ùC"wñ‚ñÿtëä»Û½Ð v2û‘I¾¿ÑEæçë]à Î»©ö_ntS,DQAóÓL×û©î÷Ó¿Þ8 d}5ÒúóÍÞùaÂWÏ†š˜¹©vðê›™î_~8ýëgfÎî©+ þ¾¡à/÷Î!‰«ÿõþ7¯&;а9ü%Œ·¡µÐð÷wnÝ–&Û™å»/?`v_~`uEte–zgnà¡­1Y‘üÊ\Ý– Hqºn‚¢"[Êß°'Sö]}þ©ý)­e!5›Tû“›5vµŠî-!!vŸoq;È?¿7éÒ¡ôê,EmŽ6GéXèïš!µNàA²eVYÒ»£}bY«R¸†¹RË$öê½ÑÜÝá^ûbx‘¬µñ<³L™Í®8q”Ǫ“ÛâA;¥:÷Ó;‡šJ»Ê"ÙFn+þ9ÓÏCëº!ÜÇ*‚G¶Ó$(œÀ9 š Ži~ļíh=ÂxdY F0× ø ñ6G< dÑ\Üa¥Ì~EnˆÏÖ%‚Ó4ˆ p_àºF`ö¨æ`NPŠÂ1Ûß#ÒÇF4ß:If#ŠgÎ5lÌôsÉðw ô\»;]UÅÛ-èÚ™’«uÏöŒ[Æ+l„¦!b3Ôs¥¿ÐHÁY-cxØÖ‹¸¶_ð–JÝÉÆL ÔRêµ=²šo,ò\ ñåû‰,ì ÿ,µ ‘ÙË<×Ƚ֪y†‘~¶A2ÓòMŠÒ,iRˆ‹¿p=€‡ã±ZÌ3ñt]î`ý™§ËWd"Å~±åú16ø¬þÅÑâa;1ÛÀÞì_¥>k¾BµØ„Ãv5p´ZÌ÷Z/ò1q´Yìá¼|£ùí­ÿdkýG'‡O•rSg•½ùýe–¥y©1>{*¢÷n‹â¸~)d¯ä{.w°üW'«sWr\;[ÿAÄ1ðr]ææø¥Ð{½ŸÂZÂ7öñZ[䢰’ LÀc–&ÿ†Fz8/óö\­”š…ë\”bc­ÒÒOjÊc-SÐ<ì>K '“j{4Øýp õÁ`«^ßëo¾{µñεÆû}Í÷Zˆ€TÒ˜jü;ýh úÉp˜/=p ü Ø –¹‘fx`ƒyð’„Mö"Î0{)§:˜“äu:݉î÷×¢6Zj@<èèî•jpê¹°Q?a*fnA?Ãðt¢™j¼™áA@úúÁÚGä5^ÞŸÚd7)yÕéàF™ÁòH=r’¡ŽœFé¯Îe´F’®j¼‰FÞ»vì>éMK ûÓÏi^ìiÒgý­,Ù³JúšZ3=l‚puIÁôõ_ëA)ºYwGúâIÒo’c;-4—¶“ÖŒ:i.sVå÷y2ÓòÑ]Ó+ÒÚž3cägÌJ²<Ä0Æéd+³è@=¿çê·Á>™±½½ÞE¹ˆÌ “¹²ÜCw¨Ž‘Ù•Ã/Gš˜5H†ç93?ó #Mp¢/[`Xˆ^ˆ"ÞÖ·lð`–<¨çõÿEhиÊü`=ä%S >Aò˜y**t=W5ÏÐ)È‚M¸”‰D(x‰`Ó³?ýPA`¼|ã®LßGÍyðÝË©Þ}гC­÷®Ö“×àp’/&{¾o*®ÊRìÎQ@ïÉS+ ;VQ/¨ÈôÝ_^˜(-J‘§«ó“äqÁì‚4õÒ˜X¹eÝ–.Ü;èVoÕÕ#›®·mí* ê,оúîØð±M7[Ë®îO«Ë›h(­+¸t0§­8¢1?8Mh‘È1ÚÊÝÍOñ1® rÏ™U'KÀ9iÜuGE‡bysßÉ÷sNZßxfWÚÞxÑádie«À×Ì3ÓSuµfËžd_£0#ÂÛb“Ž à ÷±Œ“اûy³MµJcd:Oã(u¬ØÉ·Ê ðJR8G m¼- °ýÝ Q*”k#²Ý–¤¦ÛoYF~.kC8fb«¥ÈÝ›„‚™~nÀ˜T¥CA0[ç±6YnWB¶è$H¬¡áÌÓyfi\ãÅVˆAVq/B`\Á) õÌT;ÌÖh]WDòŒ|–ø{¨=V„JMÃåæÖWröJà Ïq‰Øy¹ÄÍ@ÅY$¶ •Yë$–ÐóÏ <§¥.?ṭЈ-ÍWü_މÄ}ãR‘ÛJ¡Û²¤·(M¨Ò,!È!9Ô5Vk/ànàsÖ+%¾RË £„gdgöëO½]—r]¾Bì)H‹á¬]úOþrso˜³ÖÝn‘“õb/—•ö–ŸÛZ~Æv_íá²Rècâê´ÌÃõ+[ÛO,,þÍÃu©«ý"©·alˆGqŽ_B¸W€|#P!Hm¢±ÑÈL…ìå ¹‰Œ·šëö½ÂÝñ +³?R¤q¶[ìê°üccñ\Ö*>g4A¨ìd#O—%Já´ À#ñ^ã叨Åê?4Bc‰§œ³Fä±"@hà‰Ó:ƒvdìUÁJó¢TQI¦,1Ø9Úß6%Ä=3’!·ò0ûcˆØ’g·dK¢¬¯w/€gW®.Vå,q[žÊÎŒälË÷›ø‰u*+ž×JO4ƒå¶RÄ3ÝR^V•‘¤Ž ãÉU%áƒçM]9¼p·÷çÇ'yÔóóîŸvÿeîÔO÷{~~|úÁhÝ¡-~Eá.Ñ‚5{3ÅùÁ»’yÕùªãùêÚ¿=I¼ªxn]®êxŽ#‚Æ<¿céÒ3;bë6ivÇòòUöÛüª"}Ê]KüœË]÷ʼnòU¶™r«®mѽ{’·' Ž6TDÎ ¶â‹|绚¾¯y9Þ½0sòÙ0ùF?í|6Ö©×`¡ù‘¢Ç»ÈOaŒ÷@?ëžëx2Ò9‹7ÆX÷ãa‚= ¢¹Ñßc×y¥Œ¶üÆx MÆtãmT?a†žú'ƒäuûþµãôäÊ‚a0ÂçQáH3bö×A^b4Fa(‹È“䤯tš1¥ ÑÉÒÓ+ú32d«ÃÙÀæ¡Ët×épñFýh3-ߤßBk  ô¬+bè.IëD$n4ÒS6ÔóñizÝ¿Ódndª¶S¿ÿ„úßÜì!лÀø÷ÝnŒ¦ÉŒÊÍnhx«×„˜– †lØ`\ô-§m¦K3t)DwôŠtÙ‘ú¶Ñz®wT `€7m Œ 1”&£ééN$áü;yËì» ³=l(ÕPò™ {W˜]¦Ì'‹5pRêЋ>ù‚ÙTCS¥¼úˆdþÊú›¡÷ìù‡BÑè9³·PG'‘ÐT²ZÄÌ}}Œ7Hè±ôçt¶íoÉYfÒžÂç]IÃuxhq´£ ú²ÿÿÀóû^ë.M¨Sþ¡~z¶]úŒL<2sq´ sL»ñ71¸UÏìôf(z‚Þ­œ^®Ž©Yg¼AxSÐ"ó~ ä´“˜š›é½üd~ƒ=]äùd'„LiÚ ô<;’È%G˯÷’CåÌQ÷{}u+Ñâ4žü¾Îø›%{Ÿ^MèÉÀñÙ¾ãó# sÝ Ö’s僵d9³@ä~;Ñùn²çÕHû³!|è¯'»¡Ÿµu˜WSÛ›[§ÞÿøõÛÛ§_ß<‰Ñß–XAA7An“æï¼;ÓïJ[å/?^øõÞw?ÿpþÅÔÉ'C¯fN?ïÅ+îÖ…šÚòÈýùêC›ýª²%Àžê­ÁGÊB ã|”ï*Ò¥Hã%q‚Í™~iÑüí›#v†fh]äùÿzãô£ïýpr÷Û–½CG7m t™¨+üpó̇‰®ï«ân¶–@.íKùnVOy€'ÈaY˜ÓŠR­ÇVG*Ϥ\ë^êïÔš«-QÛ) ÿ¹Lã°;ÌóVçöT9Ïð•{Õm Øê™â³a³ŸC}ŽfG8·usèÎaœii˜ÏöxE²Ü)Œc¤–$Ê- pËM¡€  ¡÷õ€ÐœÌP4‚€‹³9BþAr@È!kO¦®2I °ðø»À𤫜ŽäïJUEú˜ Í? b­Ë dAbˆ—lŒ[s×E L BX1|“¹µÈü?´î+ø†ÎÑ2‹‰ €GÍ[ëÇ_ç/4ð— —x´3±ëJwËOXVŸx|}6èd¥–@ÛErïõ1:ÕŸþ“»Å"%Û˜ïü•Ðu×aQÌku\¨×¦9J™¬þofëþ›ÇWqan¾–Já:9MŸyvŠwV2×Oa¦UÙpYkm;X/‚v¶[ÃÒänŽËà÷ñZ'æ™hÕŽ~Nj¹ €‡çµÊÃi1 ÊOj Jɼ׊¼ @;5´oY «|ÖÅh3b¼òS…iQ,k%<€¢…ƒíb–«A^^ðÎíéq‘Ò´u€Ò)'Yq°2¦zgôÔ¥ý¿Ìžùðò‡ù¯?ÌŸýðöû¯.~xùݳé–ê2ÝÎ4AÇΨöÊðÎí‘c]eßÏlÝüðÒáùþڇߺqjÇdwùtOÅì¥#ã¥}ÇsÇ6?<µçFkÅà‘M}rîvl»ÑXv¿kÇã{ïtm?Ssõ@æTsÙéí±ç÷¥_«/üyæäÛ™S®4Üüæ04¨æÅHçÂdïÓV°Ð\Ë“ªŸô5Ïö“™2üÀhǰëñ`ËÃþø1»­áA_= ¹d¾h˜à ‘¡&pÈܺ°úY¼Êúë0Ô“`¨…Üç#-Ô&sÅ£èaÉTó£¾ZD¶é¯¿óýh”ztõøƒ«5.Wß¿R ûé ™ §5 ,jƒ‡°Í@­Ì“5ắբNº “îâÀK’œÿí'Çs]­†M7£Òs(ôÜ Y:&¯S²)‚ÙüI·›Ò+t{'ÕtŸ '/Þ¡zêA.j£ûL~Ÿâm¦'JèIœ¿;õC5Ý\ªß"«?ÅCc†LžP?ÝÎJ·›ÒÍ«ÐôŒÕ¿ÿz ³QöãSE´%úöè7âêãé½ë7©~¯¯Ÿ.0}Ì p³tù‰ìÊ`8‡þ  mj “9TèLyàô×Q¦ÚéB1²ãÿà}´ ê¥ôØCÏ Qˆ"÷o6­–lbœú…¶ÿÛi,};©PòÑ…!ºbEE¿ðD v†  ]l¢~²‘†™ # x£‹z¨A—¥ÈôÙC“®^ýÃ%-jYAæBú@Bì[ØÒ¯¦Ñ*dÊn„LБ%Âéöß/1Âàî³¼8I0£ ½1¹Üiêf± ãR-Æ#dÞõáÀqhºtEÑF™ÉñV8QÃsfl‚äSfhCÆ,c­ÔÿwY¨pŽ༜"0³À OÈÕ™x$ßÜè…†gŽ95! Ùª4DÿxÈ>.|Áñ- ¯—!üé61 M>Yrp•Lt—Ïþ¤[ÞLu‘•²½h®þööIÈëÛ'7ÜøþÐÀ‰Ê#%ÁqJ›‚hÞ–8tš¿Ë–ñ…†Ò[—êî÷µÜh}<ÔñîÇó?ß¹øbæôÌ·ÇŽ‡ìޤєã÷펄ñÚ‚º*/쌛i)†ìáNQT†ñê6—‡xïO’oäå)ì˃=wEøäÉ­*ÝŠUöñîÕ)ò¯+b.îIÛÁÍósÎW;môÖZ¶-Ì«Äß¡¥0pGçDeüÂ`€'A`™$!ÿuKëfÌ1Þ#ÞÄô\/±ù2I騲=5Ù×)EåœÌ)Œà'ÈíÓü\êܼy!;Òü Ây©jx2<²´,|ßXQ/C‘c›£Š£E1bëh>9_–ãï¶?+œS¿%º®8êäÞl)r;øK£Ù×hžYº¯c(g}ªÚ1/Ì3Fhº3Už«u f­pY®c­‚ìÈTæ„zˆœ¿¹/‹P[Å: ¿–Z Íå¬57_¶!=–®™ ÝWÊØk\C‰çjo—¯ ¾|ã(­›Ùòÿ×bå?Gª\Äî2Ï5RÏ•pÕÆ„ ‡`¹I Ä8Jcã+µòö\çéj UÛ'Æð‚4ŽŽÖŸ³—y9-M‰bWïMIæÈ¼×ù¸-ËJàeÄù䦈·èâBYöG«ÅV_ 8&6Kyc¹ÌÚÞîK'篸Þ¸Þ†ÎÎK]허؆ ¾‰ÌÇÈÛm…‚o$⬆x»/ãº-õñXêbóïNÖÿ;Lk•“ê£U[ªÉÝ8Ù.q±_ quXíl÷% [ËϬÍ?qsüŠå²âí¹ÆÝq‰‡ÓRŸu¾"#µÄÌ#å®zh%fþ"“±©Ôkµ((1—{­ñãoH qK sô·‰ ° Q˜ûñ F ¯ÕþÞüü¼Ö'ø9‡ðÌÔîkUNZ¶QY’ﵞ}Ç+ùöKtbӎꜣ;b<?Sò×ûûZ¨efBï lÖ_™Mzª¦ ?òø‘­÷ä¤h7%û¤Ê·çkf®úðaüÃûk¿<ê¯~>Óôö‡nf×襯ÍÖÚÎSÅò×ÊQÜ<»û»š"µ¹ƒí%ý­ÅíÛ£®5]i,øæhæÔé÷.ì-œl(¾Û³ózKÙСœ™†-gK"GmzÔ³ënû¶î¼À³eQ#Õù'JÂOWD_:”Ùß°åáÕº—cÝò<¼Ò0Û,i}9Þù„@^Àžf½&#£þÆGƒ ³àòök~<Ôøp þ~_-5ô×A` y2Ôø|´2?Òò <ƒ7Ï`ã“ˆÞ ýb¬z¨ àÁ•šG×0^k„¾}þ >€ÖM#a/Lv¾šèx9Þ1(RûêP’ˆ¡‚R/Ñe“ý-zyŽ>z´ùÁ•c³ýµ0à™´ 5<n„¡OÎ Ö?!£Èzš𖥂j&Ûa þÙlù{îÇIziê¡ÉW€‡ÿ»ÐÖҰׄpƒÍ¸ ’´Nô¢ËÇw§—«‚Ð0êÑ—¢ÕR¢80pé($ &M‘V=g×H#õ¿&çw:ßNwA¿&‡;ÞÝè¼™!Rä#yÀ@qp`P™"·ö ƒg‹¦¢% & û .Ц’MÝï§»˜çOŠfÀ'Sÿ‡½À´ä59-ÕäËIÒÔ§è²ÉÏʵýWýrºz\ÁØ/È/º´#‰"´…´‘´¹áhÔ[Ã%à6Ú_ß ä"õC`<ªG{õx ––¢ñTôh”Åué“¡·'õÐ`#ê#õ5ÐRÏ€\u@8ã(ÒNöËI<™NhØú0šɧ P#³ƒ ´² ´œ0˜–ÐO³i3iõèýÌ­ªžÏPÉ$ÉÅã}F~â¸êY²Ø]‡˜g€Iü½Íô|xpþ§[§^OwÆ Ôà…©.ê„AÛLïîõTó5éøù6PçÄÂõ®w·N½ûñäìh󓉿©óûº¦îÈUì*ô­Ý¹%YPœ$>[»ùäÑüc¥Q{óuE±ÏÖyN¨à§"MYWÿõñ¢Km•ß6”( ®L,P/ ª­9^V]Q™¥¬Þ»%]±·$|ë¦ÀÍYšmÅ‘›s´Û6G奸æFû$ªm{eÝïkÜ“î{bWrïö„ߺÕSµÐßpíX΃sûf¿ÝwùHúDëæŽÒÐSÛ“wF‰Z #=U1Š0îŽ^wil]¦¦BÇÚêµ7Êg‹Ú¡ÌßÈTä~4CUÉËW;$z…8|YäçhõI”˲•]¾Ê¶&;@hô‡HO£ u“E:#¹ýr÷uá<ó ¶‘ØúK™ÝW›£„`˜x™]œÔÂ5ÀÈõÖ²ÖfÀBÑ"+dø»ÇJlP–üÎs '˜gOv Gã¶vgº¦(RÍ·”[/ cEù˜x¶Æˆ9©¶XñáÜ  Nq¸O¢Ä:Nh ÿŽ$Ee¢4WçV– ðlŽàl‰à†z­ËÖ8%É­D–ŸDŠM5ž«Ä._*=Wò]–ˆ=¾òõ&À&µRz²­ûØ/UsÅB3‘Ç*1kµHÃq©ÈsÂÇH-4 öµ÷¶_aað/IA1Ùíc¤ɼV%;'…8…(L<¡J ™ÐÜÇk(B&4 Ó¹ù;é4Ž!‡‚ ß E´ÎEÀZ©àºÛ~*òZikò¿4Óì$‰ŸÄœí¼’íjÀõ0ôñÜàd÷•»³€oîîfàîaàê¶ÒÁe©…`†ü\Ëa©‡ý—î:•ÈTÈ6ˆ óx¯ö“šùm²WX™üwW»?Ê+%üÕ ‘ ÀFÌ3ÑiœÑ*OוhžT`þa»¯òr3àx¬ùsYk@AŽ6‹¸+%¼uJᆅ„þ/-‘—Fhìëc‘xÿ /³ šÄë\B|­4R#®Ó"geÒIr>¼-2JgÇg-ðø+-bóèï”Yh°O|¬rGyFJ¼°†ªñVÚ¤…yôÏx8Q÷ë£Swúž=ž ¹Øœ{¥£xæÂ¾©³{d««ót¡žk¶„uï:µ?³¡42Óß©¾,²u{|f K÷þôê-¡±Š§Žmúñâ±Ö­¡-Eg¶EO·”Þ;¹ëÉÙ­Ù~ßVÆÜïÞy»eëùm±S Å÷z«.îIêØ¬ÛëS¿Ywûìþßn}ýv²ëùPëó¡æ¹¾ÆÙ«µóƒ-óƒMÔób¸íåHë«ÑPÇì Þ? ôýùñ –¾xé;Éÿ|厵½ýÇòb¸•£æ×ã¸ú›‰Î…±v8!Oû˜64QÏ£Ë5Ïáœë«§ÜùöГkuF ŒûßûáÜßW#K/ô*¸"j{:Py\ad~¨ÊstôLg ¡ëK¦«E¯úPN`üz¡Á4KŸD0’pêKé“0 ßZ›öÚÔó…Ö xÞMw¾Ÿ!Ý: äâŠHÒ iôrúšºÞMãõ¡Uá!à®õ÷¢oÿ\-õÃ>+´~½Ð‚ ‘×3¯®w¾¹Þõúf÷ÛÝ 7º¦;^Î0P1Ó?<ЈŸ ÐL'*°á§¢g=íÀ©'I=€Ñ$ôðÃr?ß:ñþF<¯˜A§ y1ÝÑÛÀhÒ°ë¤Á0žSž™n{Æì0ÿ;Mv°Ó}àÓmä ™=íGH;™–üµ"I€¶és¡!ðPB¦wA=®T–›h£M³“Õ™&ŠÙz¡Ê¥4‹Ò/}zšÕÓ;×· ò1ÀÓÁFtÈ€qu `Ð1"áyw½÷Ít7üúñ­¡ñzNr‰1ÒÚW“ÌÈbœþ‘Ò¼ñfªI«&Z¨&­%oš©~6J ÑÇ£þhØõ¯´f^2­tÀõúFïÏw¾~5ÓáÕËÕÏ€7Àï™î—Ó]Ïa`žIüQuýùΙ×Óíïoõ¾¿ÕóÃŃC½åw¯{ûcïÛ;'Ÿßêì;Y±«P`Y–-<×’ßu,ýLÝæ±sGšv§Æ ó£ùÙaœô VªÎ}_aðÁâ°¢8Qn¤Oi²|gNÀÞ‚ ’ñ¶Éî,Åá€CEûòµÕÑ;²ýwu-Ü[•)J‰Ù•U³?ogYRf¼<'^äióiQ²äHyx¢Ÿ]²Æ6UeS³Yí³._k_¤s¬JðÙèÃ3H‘m¨JàÅy³WþK±Î+Mb“·Ìóu.bCJ´n;½'ÈŠ|ŠUö<ã1‚ IgYTeo_‚d{„÷éíñõ›Šýìê³Õ·º«¾Þ•Ë^Ÿ¡°- å•Lj·Ç«v&köfhS}·%(›Ê« #¶D«RüêJbk6GÚ|0'ÎÝiþ°//Û™¬Þ#­Ý³#Iµ=Ñ·¹"©gOvÇÎtŸ=R„Jr´žã½GºöÁy¥qÛ­oŽgú¹Ê , fŸ>SS–,·ÝªêªJíÜ™2ÜQu¡zó¬€{3;v$#`òÔïë·ŒŸÞ×ßVÙ¹+ùÄÞôí ’š¢=ªò8aI¼ ;Ä£"KYž©ÐŠ ›âùœdG”Ú1DfMŠGÁ^'r[©æk„¦Jïõ~“`•]¯­NaèîË1±_ÿïqZOú»…"÷å̱tÔ(Xa裵WɬB´îþ¾v*{_éFoÖ*ÂFçkŸ—&ðwfÙ}áhþR®!´·ëÒò¢À`µ½Œ·AÂ7N‹e$Ê¥B KãO¸žëí­¿ôpYåᾊå±ÚÑa©‹ër7·¯8n«DCGËO7nøßJ›ª²ÈH³ù¿¹Ø|âí¾ ¢UZjd&*ñ†Ð«Ô¯56ŸjÕN™) ÛÐÁz±D`&ànzÉ„°!*™Thòa»¯ä¸/óÉy†þR3µÈDÀZ.᮸-SúÊ8«}yë¡ý…&ZУÀ8-’­sÓØ ½V‚ˆ¢üÂT6á*¹×ê@‘™‚½&Áß5Ji¯ã›…K¬eN+Šã$Ñ û@žéù–òòL•Ðu™Óâ‚TaL°“\°6ÐÏÚWbЇè<‚µb‘UA^ضòÌøUF²¶8?,#^’æY”"ÚS¤™¸°÷»öÂæªÈSÕéóz$ž>–q¡.¿¹<ªk{âáÿÛßTõì9}(÷ÌἂpïS‡óú:vä{8œ»¿ ÈÇæÓ² Yq’°2M’ª²J’™ÈT\­-x=ØÚ[õuUÂÍžíÓå%áõy§v%Ýþzß‹áÖûÒPÛìPç_î]|>Úñ¸¯ñùPË\_Ãý‹Gg¯Ö=¾VÏlÓÜ ÞŠÍφ[ ûkÁh„Ì 6ÑæGðÞÆ²ÎÇ}õ¸Äl?^’x…b,7mËÂXmCKÞLtývëôÏ×O¼iC«>–g Xë½ï¡8*G=(þèjí‹GP-*Dò÷«ôÕ#—ÊÜ`ÃÓ¡Fȳaô è²ñGOVáîðæGpíãkÇ¡gûë|9ŠÎ½å"?Ôy†ê!°á‡~‚"Wkf¯§NÄ/Œ·½B™6 †&_OBÀ°C=Èý‡B/D6ª¢E Q Â$m$ ÀµàÁ…ÞMwA`ÀƒH´m®¿Ž¶ah3lýÝAPâõ†¾Â÷3ÝwS=zy;Ùýzª{=ò$Fú` ÀÀœÆ€+€4ݯoö¼½ÑûÕÆþíOIwŒ.¯mw¶áiL ómÃó‡~3ÝõþF/uâ£yÅ<·æ¹=gž- ’F^ï€?ÿp ̳ÀÇUž‘~³ùŸÿ´™¿O@T‰V‘†Í¦>ŸjŸ'8ô÷úÉhÓãÑÆ90üTnçùL쇵¸(C?\ú¹ÀÆó!­šh§OŒ~î4‹~ÊHâë?,w/DqäÒHèžw7Oàï œsãÂþ¾®Ò…ê¢Xφíá#gv0ÀÓÛj{U*FmYš.³V(¸k„&ìŸsl¾ðíxqm—pì–H=×p— = ÄžèâÔB3!kß}e Üšeý¥Åªÿ©“X‹X«|½7°l> –[fÆrtRÓ@™i„Ú:Ä×rÃÚÿîí¹F*0• Ly^kM ÿ§‹Ý®6‹9®bŽ‘ÔÛÄÝf ×Å 6È+2ÐC)Ú(å™y¹x8/WЬdBK7ǯ<ÝV±\ m¾p±_êá²ÒÍi¹§ûjk»ÓWζ‹½œ— ­“&q بÐâTèu_2þÛ™ntñ¤ìP#lš¤ñáP!Œ×dá©^”ÆèyÖö±àOŽü*ïT'ÅB¦]xÔ?ß:ýîú‰w×O½¿qza²çÙÖÞ$ù ÈâZ!ŠQò`É#šhûõ‡Óàr;“í¨€A[øþoBŠBàÏ7z‘üõÖI$Ñ_ÿvûÔO×{((¾#kUmï§Hðo7OþåÇ3 £­¿Ü<ñÓt7:÷烄9_}=Ö?ªB/Oi=¾üžö×!.M1.ô–yŒ°J„R®Ö÷¹¾Úy†7ðá¾d’Ðo':žõ×?¾tìéµÚÙËÕO®Ô¼›ìœh@“ÞŒ·£1Ð/ð髃`xжW#-FظÔ@?Ô„‚pÎ]=ŽxxüX?õ (‹ªàAŒp Æ£¸"œ¨ªþYãóÁæ'Wj‘„ AQuûì>` àÕ·3`ËžWS]¿Ý=ýd_„FÐÂ›ë½ø¾ãý}îàõ“{§NìžìÝ5Þ½sæÔ^`ÏO3'€LxKÜ¿tôîw‡oŸß?yjçô™]— ‡{ÊGOTŽÜ6õuÕÝK‡_L´¾½¶ìüõÎiØð<¼V=?Æü@âP=ÙI5\??XûÓLÇ_~<ùîFçëé¶÷·»ßÜîšo˜­»?Týú^ïÝáê½Å¾Q*#Áª”PäP¯H•]v$çjïΧS]?\>öþÖ)|L¯Tãë†?ZÐø®uU–& "¥¦¡Â Q2³h¹y’Æ¡zkäýæ÷w/vÌŽ[T¦)nÝ_|²¦ 0^X‘ã—ã“æ™åÍßVVw çèî´ÜDIF¹;rýÊRÅU9ŠÃ%º}›KS$Û³UÇ*¢jwÄ2¨4¢ioje–º$]YU\^ ÍNågÈ÷ïŒÍM“$Fy”d+WFåFsZv%¶VÅוGn‰åÄJMË’¼7Ç°Š¢=bÅë356U)âXž oÕÖKb}Ì#ÙÆ «/ÆRÛ-Ó:¯öw4r[è´Z缦PËö³].0þ4Äc­Èô¡ÉŸP*’mÄ7úwQÙ.Õ¹®æ¬ýƒÀôs‘ùbÖê?x®ûw‘ÅÒoK¾åbŽÉ'Ñ"k…ã o³Ïü=Öñ,‰m¾Y/Ĉmdö_ ­¾€­rYèëªt^íç¾Alû•Ìaã‡ÕQ"Ûp¾—ñ§þÆiþ,•«¡Ÿ»a’¯#¹}ŒØJç±.VjSÊZ¦ú:m‰fjÜ0xæ[ÄJ¬K£…A,ÃÊ8éÞt¿¾E¢Ô¦qkÜæo¥ý²¢HŸ(ñÆh™•ÒcU^Œ†»^î¹J'6—±VÁV²V‡Ê­øÎKí ÿž¡©ÄÃ@êµÚÓnÇñ ­Ä<"À1P±QÉß ÷ÞÀq^&a¯W;hÄ<×åÁ*›¬X^¨Ÿm¬ÎEçká'\ï+4ä°V*%æBî:O—å^®+ܾTˆ-XË _ç™=×eµÌÛlKVð¾m©IÑ"­Ú™í¾ÆÎj‘»Ó >ÇÈÇk½·§!ËÅ"à ½M¨Íø×Y®ÿWw»Å>î+Á<2Ÿ J¡©œo ÃËi©·Û h'«O}EfðˆØkÃu®Îv_º»¬`»¯Ïx¹­òt_ÅrYéêô•»ÓrÇeÎ+¸^ëøœ >r^)± SÙJMelwµF¸†Ðm©¯ÏZ k¹Ôsße‰Vb,·{|…Ü­“¿Ì—“r ̽œ<…^ oB âî|W}ïríÝ« w®6=½ cíÂÐéA=äá@Ó‘rÌa¬íñP<Ï':‘œ%ðÓˆÜYB>móã9ägvZ!0‚%@íOÆÛ ŸNvP›ÊÓñöy21ÞøàÊñ‡Wké¤ ãÍÇ}õO ¢ÓATžµÏ 7>¨Ÿh›i|L~}·íÑP=ôÓ±•CîõÕ<¬ƒ“ ÒÁfà ðCPü1ùõžægã­/¦:žOÀši êœe:¾WãíQ^‘…€Ö'õo¦»_Ou‘íCLzü9`Ï@=’Èúéæ tEÅLSt¼ûÛ”³v@«¢IBGÌÔb 0h êÇUôBݰôŠÙ°„K¼»ÞƒË1›—Z(}éI†Æ — ™:‘û¸¯–¶‡ÐÓBÚ0FZ˜EfÜø,o/Ç:˜É·¶û—k¼xìѵ†£Ý 'Þ_ÿúÏw¾ý|ë$z×´µ¿·í©.4Yïoô’Ov½k_k{;ÑùóLϯ7ºÀ~5Ú ¹ï§º?Üûò×Î|¸ÿ-Gü¾uŠz €ÄÃùËõ^Ô£¯IZœÐ邆MKAP ñ”E’´ ´XâÞ…C¹kµ/W£a4òÃ_#ž‡ß½qz×ÌɪÎí»õõžû£ZP ¹ |)úëA&(‹0ØoÆ)ð´Ã®08Ô0WÆÚhm ¯÷ÓÆÓöÓçþA ô¾ƒ«@cxB'E_´Q°ñz¬´ó‚¼ ™ ‹¿c]ÛnŸÝ¹}îÀß»T/Úã†ãøÂâ Ôùéöé…éî×j§Ïìyp±ææéýS=»»çÃݳ“=Ûª üÎËé©èë*½Û_}w°æÑxÓíÁêÉ«›%DXFjÌ‹3Å»·Fg'ÊCö2ÏÕ›“$3—jnžÂûçÕx'þÌ><<ÿáÑÐ2ÞÝ;ã·%‰WD—¥Êr"Ù›¢8ô÷™U”º¶2¾÷XÁÂðͱ’ÓÕÅþ!^[Ó•*¢v‡V‡T„d'ËòÒÔY%¹iÊâ ^¡9ÑœâdÞÍššŠ#á;òý«)O—ïÜäW]]_•p´Ì,Íßq½.¡‰ßùŠÖ©ŸÒWH×éàAÞßôÐòÿ"ÉfŒvÊEúfÊb~Eí÷é#æGrôe@žöƒ+u7Κ8¹{ú̾Ûß»÷}ÝÜ®Þóv†\â0ã:x¦‡6^ß0ú(hî3‘õgÒLï›‰Ž·“èâ{ÞMu î|{p¢»òæ×{nŸÝ{öJÍ“«Ç!èý ÷/A_ÒŠ þÎíG)™í¹~â·›§Þƒë˜:¡_ƒ9':Þ~8ñë“иNÄCS?ª0 ÚAò=a!b€%˜ic¸4lZç_nŸAAÚ’¿9€6@Ïœªºuv/œ(¨o­„@  t²“@ÎD€Jà‡A¯K«¥­%mcf~pwtê†ÞEh.e!ZJ~˜'@€çõx'ežfzöýtïsf²(ðÜ¿xtæÔîë§÷LŸ¨ù€X†Ú¶uVŒõî˜8Yuýìþé³ûn~sðÎwGï_®¹}þðH÷¶ËuE“]ÛûНÖ µ– ¶”^:^p©¶ûÛÊFz·OŸÝ;Ô»}üì¾»}µ·.UOŸÛ¥¡ðrmþµºÂæâþ†¢KÇó`·•^®ÉÃG<ÜR2Ò^6ݳ}´cëHké`Ë–s‡2çúšê‹Ãv‹¤YNAܵÉ»ëß}yëôè·l M w‹ÔX§Ex¤EyEëXé1â]Å‘1®<ãó­e?ßûvüÌî·7NþøýÑ‹µ»âÏÉš<½·gOÊ®,uUŽßâª<ÿ½Eºí9깚¢xa¤Ò¦4E±5Cµ-+à`qä•î=cåñ©Á¬¢é΢ ²l¿âLßÜdEB„wR$?1‚W”¥)ÊTgªóâÙ‘ìm9òã•aÝE{7-8VqxKðѲ°šŠˆ#%!Û2åyÑœ%Á{K‚2¢<ËóUÇ&í¯ÛÍ-ŽT$K{ªRê‹BFÊÆšÊOmKl/ðÍ‘¯w'ön‹iÞ2÷ݱéÖ²×}ÓÛOìL˜ïk<µ+©¶ ð‘œc›¶Fps|¦©OìH«Í+à7GïITÌðo(ŽŠö1‹åYlÒ¸…zgû»—Dˆrü=£xV›Ã„{3ƒŠÂi¾¬D©K¬ÐvE¢:žx¹C0×´,^¾-Eä뜬rIõs+ŽÃ'³%*tlã0žu”Ø!7TàiηIñó„Ž•9ÇHB|6–%¨·Ä*ƒ½É?UOà¤øy¤¨]SÔÎ {H¼Ü.É×1[ç ÚÉ òªLVf²‚¹Æi׊$E¢Ò!GËJ–ÛïLö­JQåé¼@;¥Ñ¢ÜN‚¯C‚Ú1^åàm”Á [K-ã´.!² Ÿp™•?ß$Zã¸)–Ÿå“æå/0õ˜DhÂT¶ w­Zdªäxü%–Jž‰˜½NäµV-4 t‹cEh‚|­439omx€­Zh¦ð1–zoÐH­¢t¬Ø/­Ò°¡‘Û±ÝÖ¸Ùq±_æåºÖÕ~×Ó#ô6ñr_ÃöX+æ™pÀ?2¡%²xl#…ØJ-·ó÷uD˜—ûjÔÌŠ t Ñ8†k]T"3/çe×å,‡%¥À$Ø ÆKŠäƆxêTvJ×c­·û…Ð2Ðךë¶ÚÙf‰˜c ÚqµYâj·”ãºÊ×b­óv_¥âmo¸Ž‹ œÕ·¥ÞN‹}ÿ?ºÞû«­dÛ÷ýÞo÷œ{ß {Ÿ:8ÛØÆ`09K(¡œsFe!"‘s4Áä`œsNƒsìv·;wïpâï÷¾KkŸ=ÎoŒ9jÌš5kVÕ’P}VU­EÑgò’=jÞ~{oIÞoe¬ÝM'ÿÓâô_3r>áî4ŠÓlŠs?hg¬Õùç/¯wVkdÔ=!+Ë"LÓ³“\ò\›8­ÚÆðhs„ŸªØ`ËfYŠÇ/aìfm0wc8lÚn}7‡¹‡AÝ‘ý[‰,ÇRÆ/¯T—¹D2YžÙÄpÙyn;'àä×xø³þqûùöqfcúÕùÎÕVãjÌ|¼µtµ×=ßQÖäb»Y>Mþ|¯¿³JÙ]­Œ†+V§k#~ ås©(¥ªBÖÞlémµµÖ(›+¤Í~‘UœqqÆ[í~QgDò ”Òt‹'| 2´dƒ4/â•õÕ×FC/¯ó`Àóýƒù×þ°¹òÓúò×·g€=~¶öáî±÷w¦ ààÍwçâ+<±«:›‹Áò7ë3M8ßnÌAùéÉòÇ,„d~Îý‘X9!ä›KÒÄWr €sHÔ!åãÆÖwÀ9S¿<]±|u œ÷w'À-ñÅ" …xóÝ rH°ù™x}4±ùéñž-o?‹//E¼Š¿ñîä×·Æÿˆç¼ˆm©{ÇÞcFŽ?köÍp hü«›Ga$Ÿ,ƒ>(øùÏYxÈ}±‡â‹ESä1òì(ÉK$Jý7F"©†Ä’ûkÃ’˜…ŒƒvÉ¢”ì!âˆK<È¿ýÇÇ ÿöbõß_®ýë‹“ðÄgëÿÕÍ©/¯O¼»zôõå‘'§ú6Ov¿¹|ô祷§P‘æ_Îù3±Ô³ô‘ÇÐ |p}H®@ú§ø þýíÉ×ç{.6­/4>>Ñúl­”§«mÏâ,±u<¶± !Ý\iùêêȋӷ§kQúüTÇ»K?Þ= @ŠP?Ý›þîÖÈw¦`$í0B¾½9M°ï²=Z@ Ù7|s¡ïëk£o/ö¸>öÍ£_^B„÷W†Ñ¥‚ ç÷ç"–›á‰Î@ÐC²uT$uÔE(TGØÑIdѤØ!У{u®U`!ûLÆN*Ha„‚Âùù©®gz9ÝýtµåÕ¹¾w—†¾½‰ëpì««co. ¼>ÈÝO´Ý›m¸?×x¾éÁBôÞ\ãíéúSµ×À'³ ·¦Ã×'kn«»3Aö6²Õ³Mw&«ïNÖ=œ®Ï5áÎ EONw=Zk¿»Òú`­ãäpÅHÔ8Ùf›ïóÜ9ÞúâlïãÅØútÃæ|ó‹/W»ž­´=Yj}8ÓøîlÿÖB BÝ?Vÿ`:òt¹õé ¾K½3-ö *Ï+Éðɲâ4§4}$j>7Ý0³àçÔ­Ïkª”t5è*œ,W/Ë-·s­Š<½ yq0ðàT÷X£®R“㓱pöëh»üŠŒ±ˆ~¤N?Ùl­)¥¶¥WI[¥¤ª”Rë`„l—2{°¹4Ä*-òÞˆiªË{b<<Ñái )ºt‘rQ‡[éä”ÛÙuyµOÜP¥ôÛ˜.}¾×Tˆ Ýuòᨦ#$jð0¢^N¬œßQ)+'šCaU³»d$¢é Š'ZKûÔMÞp«i´ÓÒTÁoõ û+e=nÁBÄܪ)<µ¯×ÎdóA器uÀÆX ko­¹Óç»;Pþb±ùL»íÝZç•~Ï¥.'ôÙ|ºZÞ ËróN4”N‡Ô£náùNO—‰§MÖh »=ÜÔˆ®Ø Va Hó<‚LUîî YA›KZ.Ë×%šŠ“5ùûõ”!-m¾M‘93jw*DGünAÀ/Ö« ´²<µ0C#L×òÌŒSãµßn,uo榌{³z´üóÌwÓ_ßšùiãøw÷翽?ó f–³?n,üôhñ‡‡„åË[ã_ßú~}îçÍ%Š`|™÷!xfᇭ%RÿéÉÊ·óÿ_ùîá<v“8P ˜ýöÞô7˜¹Ö<˜7H;ñrÔû˜²gÉçRÿôté‡{“HßßGJ<Ðzw‚|r:„xÐõÞÔ7Ç`ÿ8mB<{K<| ù(+Tÿã“eñ´ìú,¹µD>¥ò ±5÷í½)RH°ùëÇÞÉ'Y EPPX߈ïïÏÅOâ“Û=Déó“ÿör|žÿþêä__¬B'7†â»WÇáó¯O‘àÿòŸ^PÐî‡;@‘aÈÛ+Co.¾¼Ð÷ôL׿ZÛÆÉØ×Fží~rºE_ÅßD„^‘¡|ycŒ¬ˆ*V[ï.4|¸;xü$éß;kþ0øîÌÏË?V A)Ù+ÜÐg(ëÀ¹Å&ø (%Ç O vMcàPHO¸¡ 9 X6cA)Y ¡áîLx}Dä0ÌÝ™Èíca¤Ð!›Ç[!ÈÞ˜¨x¹6^} ú|ÓÃeD7¢w¦ëoNÖ¥³ „ÄqˆÙÆ{3 g"ÇÂH×g#·'k®CVÛªզ ]¾Y˜"¢î`åüŽ™ý[›4íÂhõ½™¦'':!ëó-×Ö¬u¸æŒ§:Ýb‹±Íå¶GK­÷g£[+í÷f[æc®§°Õ#é,WVj©åš¢P)«LžíRåÕ¹øÕNnc…¬©RöhÆÍ]›«¯ÕeÕRm:jOÜÓ`ïpv†”uVz™8µÕÏ›ŠYÇ£¦˜WØìâEÊØ3îº2V¬\Xïd 7*ÌE~}^¥¹Øo,Š…”ÕNv¬FÓR…<ü:¿¸·Å ½%(ïª×5WÉ[BŠhHi˜êüÒGè³0¼&ŠÏTTSÆè¬Áí¡"âf4zYíüF6Ñb˜i-ªU4;™Q=²Ó¸õ5©‡Û M•Ü&?Ç'Ϩ”d´è)§cŽ6eÁ1ärsÙ¼OºR¥™ró;4y³å’×óÍ·:Ý·Ú]·;O&«·¦C–o«úâxôB»éVŸëÑT(ÌO´ÑNÖig*$ƒ¥´“]›*»Vp¸N™§Ìø´J–Ó^Ƴ3YéýÂŒˆŽV!ˇÄâJ%E[´ßÁM·²Ó%Y;Ú9YCKÿu©<Í(MUðŬ}|±HUªËWJRKh{é”=Lê~‰ ½®ÎX[£D,õa£×#¬®;LÔö:Í‹ÛS???ñîÖñôÓ­±éJÑýÑÊ»ÕÃU⨃Ñ[§Ä½Ép›­Î-pi kʸ± ²­FUãæv„5½­–á¾òh½¡.(+ÓFâ‰WKPÒV% »JÊÍEkqÀF·ªóŒê§éuðLÚ"ƒ2_'ÏÓIs,š¢—píXýÿýýÝÿýå¥y¶úóCâÉqâÑ­;³_Ýœüðpñ;â_NÌ~G<±@²Í÷Ä?ž Œß€…îÏ@å'bËiéÛÿàùðp„óýæ",п^Ÿýðhñ{âås?‹9˜Ù—æyæ™ùúöÔ7w§¿»?ûñœ×ÂG"ÏïO~¼D¾š ˆà!þcEü}Ë(%X(þгã/F#^Ëg›Ÿ7~Œ¿|û,~vC²Ð¿¾\ý×WkÄ›I6‰šÇËÄ ãÀù!þžCòCŸç%å#¨ý"b9ñ_?œýžxÝâ,ô?ûwóä)ŸâïH$OÝ %‡ßžxO.Ý<úáÎ$jÁC i‡VyBàMûãÓÏÎõ¨þyz¶{}¥ùÎ|œó`9 ÛëËýˆÛS¯/ ¼¸Ð‹&pÍqå_^J¾¿1ùþæÔ« £Oõ!ýæîìóó}ˆPø#,ª|úFÎåAADP^_èÃÄýþÊðw·'O×:HñÉj;Òg§:_<Ð ý3‚Za|0ßp.òòlÏã“`ÀEÏgºáö2'ˆ)JD{u®—¤øÃáE3Â;ª#| PàðæBß³8Pt¶çËËC¯Ï÷Â)Œpu¨ BV|ç"”>]a8]['Ú ò‰óÏæqb%úúBôÆdíÕ£¡ÛÓh }h¾5¾3K,àÜgá¼±HÈÖbìñR+äÑRlc.ú`¶ñþlÙðéºÍÙÈÖlÃúløÞL˜èÀ|ãõÙp½¦çP1š¼Z/PVbgâκŒŸÒ_!¿2¾2žm´µØKªù~IzXKªRÎ5Ù/Ž¡?õH.uݘm®6Ð}Ê¢z;¿Í¯ÛxÕvUi‰EšíÕ7U±jm[ØÞ0És†âº€\.L7ª }vN¥ƒ×ÒtÖé#à„*yÐ\usc>A£“[gcÕ;øµ6ö±.ïp“¹§VÙZ!^ôGËø1ŒUÉ{ô ’JgI¤R^[.ºy~'»ÂÃ3*²ÜÆ"üBzL” ;£Ú#h ){¢àÏʈÕ(û¢¦ÆrQµÖS§˜h³t×J{keSmæ…ž2ÈRÀ3ŒŸèZyoPŒt$¢n ðÚª„‰K—«äkжéó·7ë)ÏÚÏ4Ø+•ËʳaóÉZíÅûb•b)¤ø×S‡‚KŸŽ×\k/ýz­ùVoÙ“cU_o‚²9Qùz¡¾Y–¼’nŒU^jµö ç¢qëtÔÜm/±'X© >þ uP–ëæ¦ÖëŠë´4ñÊs]‚,Ma‚O’PëŠÛ½² U‘š²ÏÌJvŠ2Ã¥%]å èvÞŸ<¯J[ÔP½²\ø˜˜I.qV¹’bá̘X)HÝÒ3Q"H¥äï(ÈùœV¼ÎØÏça³“í¥,¯ƒ¯‘çªe9 ÊÞœôßKGXÅ ´ÂÝ…¹ÛèE{8ŒƒÈ–Ðö#-Èþ,'åWIÿ‹U¸]ÆKbSvux´=P¤ÜCjQª™›úÙ‡ÿ>-ñoQÄ¥ìrh‹ ¥²)3‘[°“W¸[ÎÈòÑ ŽH9@¼í‚¢=¢â½¼Âϔϙ9¿¡gýZDÛ¡`ï“—ìUó4U;ª®ˆ¦¥Jbˆ¯ð(9 2Æ^qÑN9m¯ŠžPe¦÷Ôhˤ™ü¬O좴õÓïï.ž=^ê)¯33¬l‡0M]´;cÇÿ¼÷(Ó©É;ÞZúèò ‚µ×k*ð ­ª,³2C!<$ÒÉŽh$):âüL:«p‹²¯±ÎXS­”Ë.¾N[Xº¬ô–:õ½óý¯oMlï~_½?5¾]iùò\ßñN[G… 9À¯°Ò\†‚ ƒSac5¤íµºž£ÛX8Òáë÷Vx…C½þ±Á@K­¶¾\X^Jõ› ûõõ^ns…(Z)Ñ KJ–»8À¤-€?R­"W)ɲiv‘¯.·þË›sÿöúÌ¿?;ùçÍ¥_Ö~z°øÍù÷×ý°¾üÀ ˆô‡õùŸ6$˜dî—ÍeèPÈ"(??Zúekå›õ¹ï6~Ø\‚|Gü—Bÿikåë{ÓÐ~¼‚ì×÷g¾º{ ž°ÿ¼µŒ°dDFXÄåë[“`22²("å—ÍEÀø“ìÄòÂÊ·Ä›– ¢øîþôâü`•Mtoæ'@HüY0¤?p5KZ~"ÀcågâßÓ=Pú÷‰ÿ¼óÍí‰wW†Þ^üúÆt¤n¯n¾yô/rcŒ”ïï/”þkùöÎ$äË«#(%uøu}åééÎç …Ó›'b['[Ÿé‚@ÙZmƒ<^kG,ðyq®BØQz²E¤3”G›Sð„ë @‘¾—˜š?g»QÙÀLºEŠŠÈÞ_lDé¹zȽ…†‡+ÍDVÛ®DŸŸíz}±ÑÈþ¼¾0—á'k=[':¯voìy¸Üþ`±õár+æV²·èœÑ(šFÐ4Ù=²K0’·ŽÇÞ^øòÊ01ﯴ|„€"¤¯Ï÷‘àñîÒ I;›qì¬/6!{¾ž$ù@A’yöëëc0’AÞ\èG ý‹ËC$J‘<ƒºÐ?’éOØÑèû«#¤ÿ[ba‡H!pFüA&h6nLV“¹7ÙŠ¯ÀÀñ V‰ƒÖƒ…F’ß`'¡v2†b!© <äîl=±˜;€´¶ÒB® ‘¥ðD»°ÃBƇåã σ…(0æÎLäÞ\#ŒËÞ¾u¼ ` ­ ‡-÷á¶½c|W+Ž:-[K±­ùÈ£Ùèýc‘;Gko­¹7U¿1Û°5Wÿx‰.îÖ:ñQ^Ÿ­Ÿl6»dé˜}tÂ$³*˦/4iòÍúÂRM”¾×§ÍùÅ-å¢JÅ.M÷(r*L”šR˜Ä«Ì Yè°‡ì•ÁÊÕ±ÚÞZS_mik@Sa*q* ]*Ä"Ë/7—t„ÍÑJMÈ#®(ã»ÌL¥Äª£€vÌZŠY](e'¹ Å#­Ž‰.÷T§«ÞÅmò *ŒEeò¬ 3½Ñ/óh~Éh‹µ¥\Xm§6ˆ´Ù«V´T+ÛíQcm¥Ô¬Ï·˜ ]vFU…¸²\èµ2|†M[à2Q+ÜŽÓÌPeO“©ÖËïk2Oô¸»ÂÚ&?(ª_ôNv”Ž·§Z#å@d¤N>C¦[ “MÚ6?»½ôUè×gØäIJöŽ sž…Ÿh)Ù_.LYˆ˜¦Ò¯[O¹Þí¯RœoµŸmµ^êr~q²óΠÿL£á»S'ªÅ'üWÛÏŽUn—¯»nvY_ÎÔL:i'k¤·ºí¢†£vÚZrÆË¹³­ÅÍFš¹`§!o»“™Ôfãš)û¢Ì •[æfA̬$·8Û#γrÒÚ}²h™@CI€Ñ#É4ÚxHK‰-­ì€²°ÆÈðÉò ØxGZ\"·(×)ÌFj㦛É>iÒv¯ÂHO2Ð’*”!23ó°C˜ÔRkMô*ÅÎO-ñËs1oºDúRV‡_:ZoöÉrÌ%IðñJ³+ùÕZŠW”ia%![©)4s’ÙŸ¹IJÚ>s¿Ax8hc†l»*GT¼›“ý™¬xŸMž1R‘ê…©Œ¬O”Ü$9;´ÐöùuÚ¿áxøÅ{`¤ç~†T#:,ã´ëó ¢’ÝÆŽ =`f¸õ§¶È®.0Ês:›,ô‚]Bnrqáîü¼íœÜÏ(´ýEE{ZŠJš-æ¥ÚL '¹¸`à'ëÈos3>ðpY‰üìϲÓ~W”·’·=3éïü[.mRp8?ýŸÀ6 ’¢¢’Rn"-ïÓ¼´_‹Ù‘•pÙ…Ép v•än£¦ýŽ–ñ €GTœ€4?ùWPt‚49+‘·bÚ~熗(!Îðü´S¦É²«3¡güc¥,£¬T(:á!nÁ§F ¸1OÏ=õŠæzüwÖz7ÏŒ4˜ñ§:+;=Vw¼¿Ò)LÇG¨ÖWëV‚ýµÊF7³«J¢gïó¨2ÎLÕŠŠ>×pøM”RyºA’ª&é¤G,Ú\½"ݤÌ,UåIÙÉJ~zw«Ûï–Ȥ9*ežAOÅšŒA·ÊÁ š G´o¯ýóÖÒZ£þ\«år¿÷Ì€çÄ w°Åà·; ùÎ’›­T€v¦z}VÆT¿¿³Ñ `h‰è½6¦A™r±k½œH9?âãu‡5±jYc¥¤¯ÕÒÖ–ŠtÊ÷ ßÍ5jól¥ŒRÍaåÊEŹŸ5$Wç£èÀ¿=_ýß/Öþykùçõ…oï.¼¿qì»ûóßÜ9öá6ñNf? P€%ä&ý»ûñµ 8ä|ÿhÊ·ç¡üôxåý)(xzòÇ­eÀÏ—¸>÷‡'ÇQëËã/. @Þ]û_ØùGD#›#ÂÆ¡ }¨K nOBÿ·k?K%À-bÐò§'+ èÄ+X-¥°À‰gÁîNÁBòÒ7w¦^^ìui¥(zv®Áô½qœ`†'˜^‰©ˆÒAsý£1²”daÇDl §~x‚(žžê„à„àCðÌ©NÈæÉVHŽ·@‡ñÁRY!’ÍMÇ}È"„zsi€à“x|>êÄ!?VÃÝùúƒž#&m‘üsm²j}¹‰ŒŒž£Öæ UÛ»Ëc@õ¥ÖÍÛÿt­oóhª¥¨Žb\Ä?û¸>úÅÕaèh…èÉiÕ Ð1¨[3áø¦O;$¾›ÓòùêÚ( áÙ©.Ìã°|\~`B‡ί‰í¤NtbŸèt7°„¬„XP¨CìO”Ò½y<††Ð(t8 :PŠF‘’üBrH|ʼnX BuøÞÄWâÛLÄ~ >ð!¨,Þi'm`G–$2,èB,ã,6Ý!v£šþk'¾Ã…ôQrȵ)RG)þdçá—Xð ßžßœª%Oã€pÈ39$í4wcº®/$UÓw–ÉÒ‚eL¯™ê.¥ÙLÅ&}¡Õ”KÒÜfî¾!奴 ©vrÂ>“~¦šƒòJÓ­/ì k[ªT >Ys…*ê“Ì,»"SŒ[_l‘çV» E Œr kü§‰æ0;MŒ ·HƒÙMGu[˜+#rµƒõ‹¦»=Sîz¡êÜ"¿©¤­F7ØhЏ9A µ=$íßh‡­ÂÎЈ’kÊÝ­–îv{¹—Wá¸Ýl¿Ÿ_”Ö–‹k¼B¿Ú©õ‰º£&ü®ÆjT±²! j®”ƪä­AÉ@£~²Óv4f‹ê†#ÊÞh4¢Zî¶u†B’£a%¿êH…6çI³ ”)“­²Ärc®–µ«B“S¡Èì÷òÂúSͶnsñÝÑÐùvÇr½öJ¯çÕ‰ö Ýe»Ênõ{ÿŸ‡ó§ÕÛ´7»K¿9{:UñtºúBTûõZl©’2,¿Ùe»3O{8«aÕ„ƒ±V¯?Þ`šªÖ6k©uòÜf}¸\©ËÝQ)ɯ1d¹.~†[˜åfºDYvn†OVÐÔvøä²¼zzb…º¨ŒŸî•å‰Fæ! 'ÅÆ%Öyì¼4ýŠüÙ6x¦ÖPÒhÔèY`žj3¨¡†­`'UA‚¦è@•–6qœ‚l? UB:j‰Ô™‰&Vb¹qòV¨ `ïôËj({a‰XÙáRV¥2¿FK è˜dÝâL—$Ó)Í4óS$…;%”]zÁa-/ B¼]0ëwì¼OYÙ¿×s“dôýââÝN’Y’f‘gs ¶IYDô=¬¼ÏÄŒ½zi»h[îáàÓö  %ÛeœDµ Y¤ v>}7¾fU¦Œ› fï‘Ó÷óòwÅéa¯$XÆóYKd¼ÃjIp%/ë3JÑn*eOfÖ'‡ÿ:?WvÆ'¹™ŸädR”»V¸›^´‡;B§ìeP÷IiRa:‡q@%JcíÕdü†žÿ9ÈGÆK2È3aQ‰R5’4fávjö'ô¼Ïà•ôwÔ´ßpr·Ih N27o;=ã÷Rú~Aá.¤E‡ÅÊúTÎ8 ,Ú 5;‰_¸]^²ß$IQó¬N‡;ªz/ìãÒ2å58´ÙÂâífÙ(2Ö^«2éÉÖ±Nµ9^^ŸúâÞ±vg~Âÿ00<»4>µ]Ÿ‹ Ógçî.—µ¹…[§Cª[K-/¯þôôø¥¹zNæ?Dý»4u(jèª×(Ø qJ™6תÊR‹’]¦"¥ ‰_´§(õ7iûÿÏ*¯¤¬”U]©jŽÚ ]îH­6Z«“Xdi府 Õ?ã ßj}0ºØï‹™Ž6VƃÇgêæ¢—ç0:µùø‰Vʪ]ì°O°8Q#`$8LÔæ:mS­²=¬ :#íÖ µ%$i®7Å­õšþ6[Cµ÷eC¡^,UUÈe\»«’åU:ÑAÞg墓m˜¿~x0ó/Ïüð`åË› ‡%ÀÆ/›+ÿò| 韞œ€|¸s ”òýƒyX~X_xsâݵ£pûãããßÜŸùúî1ñµB‹ß=˜#³?m.¿½~ôOO¼¿=ùîÆøë«£o®mêzyqðÕ¥ágçúžŸï{u Ѿ¸>ŽøùÜ9†ìW·&¡Ã‡ú¾¼AÏW·ÆßÇé…ØO¹9Nì|ÅŸ[ÿp „.¿»6úöêÒ7W†A5ñ}Ÿ®­S¤Àòhµýîbã½¥&((}r¦ Ê#ÌG+Ä ÉæñÖ'«Ä´õød;t¤ñ)/ö`± vÈÖ‰¶Wçûȃ‡ñYl#¾vñîÒ,Ä"Ɖ6ÌV¨B.2@y}¡Ÿœ¸QD†%×7¾‰ï¤  ¿¸<üå•¿¿3…*¨篮½¿:ŠF¿øÏp“âaœx— äR‚@‡=¾ÛÒŽ°˜1!PàI¦ˆóæb?9;#‹V6ˆÕ`CûÓÕžWgß]ƒ¼¹0öêxcäÍt‰ºúÍÍq¤èÌ··&Þ^$®ÌB# $è0®ÀúRcG·×‰CÈD‰}¢+#¨‚Quã´C4’WþH ð¸8€°„[œj^§tˆq!2uaG)éF‚|nÂ-N 1(èŠÐ.$Ž:,Áˆ*ñÅ%Â\B–l—@²åfÈÁ!ćg‘žäÐÈ*P6ˆÅ«N²‰jD(t2¾~…ºwfê žâÇr +0ñoËF|Á‡lå/G}ˆM®VòÓ„' N¤œó´³cÓíézÁž¯Œl{£!äãÛnK1” 7Ûg§ãGÌk,îk²4•Ëê}âΰ©I’Yã„\¼ ƒÓT€=¼¥ôæM­Oìµ2¼vn( 7« ­º¢úrI•“]a¡×8Ù-Ay“´–,Lð’N˜é3³;Â&»2k¢³¬³Fñr'»]=.üÀ:Œøl‰èÂÕŠrÁ9á°º¶V‰´Ò'²é)h M—*2½&j_“¹¿ÙÜßdª÷ ÐPOD×Û k·Mt9çú=«cázõP²+ lq2;|Ü6WI—?3mÐYy<Ê´&Ë*M6‰BôíUƼî€ðxGÙˆ_|¾Ó¹ÖbY kW[-ç{Ýgº×Ç÷¦ª×b¦—+Ñ›ƒÞ¥ñ>Ûãåß_ì~4^ñv%zoÈ»~4ðx*ôj±ñõbS‡6çTD?åãOyyçÛìÇ*ä÷'Ú tKÁîñ ÚË9ìb%ûé#Amµ†RobxÅ™ª¢}I޼`8{{Ô!¬12ôôƒŠ‚=6^Z™ Ã\rØÄJöJsíütŠ4ÔýV,¯ÁÆÓS÷·¹$uF£…[«§Gm|;u:ê4Òº™ÕZZ“•gç=¨¢äÀ˜€ªÀÎ?Ò7; 6,ÕšOžuðIÅ-É´p{¥ÙåÊüúR–¹$ þСY‰Fö!Næ'%¿#À†‘ ¦˜¥f‚œ}@Å!þ7ºŒ¶‡•ý 'ïSà×PhSfs >³kr9…Ÿ‹é{y”í2îA¥àˆ¹OÈØ«¦Š™ûYÛ‘ê%Bz‡ºÃ¤È”óʸÔâC2Þ~£0½T’‰o¦¿”%ã$+x)Æ~™ UœÀcñ𸇩Åû@;NŠ:¾™¥gòK’¨»ò2?¥æïä0–ÐöÃU(y;Š w ¹)JI–Bœ!á$)Ä^RuwQæï(Y¿ä •rI8‰Ð¹Å{„¬ýlê.ZΧ¼Â@«,Û¥-R•*ÉþLDÝ ÎÞ(Y‰fq†[G±Ést¼ó ¸x€ºà¡ä$Ü:Û»<rò7þRJ¹…ª$‚v*lÅ(edÿ©ŠàRå„í%«#¡«ó-Ý!U•‘ºÔ[þ˳³ÇZʦcNaúïüÔ±°±FOªÖ]8Z;P­\ésoœîþñÉq»ðóÈÿ2óö…Õ£1 ðɦȴ*3U܃*^¢ËPh”¦;4Ê^uÿp§?Ò4E+»¾F_R7…õMuÚXX[ïxt¹Ãº6æ"Ú¹Æ3ýžÉ&}[PŒ;…­ ºp…¸)(³È3:â†î]O£náhЪÍu•Òb],¬i +£!ÉâxeOTöqÚŠƠ(èfÕWŠñ[dVçÝüJ//R«®,—šéN'ßåÔUn3C@Ùi¥ž*ÿÃÖòωÃ3—Þ]ŸüããÐ~´üÏÏÖ |ycâã:ÏOK‘Ë;?n,ü³±ðÝú܇{Ó¤| P¹9ñìqý( gc­ã«;Ç C^_y~‘XØysuôíµ1(OÎô>??ðæÊ²OÏ‚Ëfg̉gz·Nu?X‰=Zk~¾ïõåA¤OÏö¼Ä„xiúæÜÚ‘Þ[Šn¬¶ÝYÉD‘ÞÇ|±Ü ýîbÒ¸?Û8ÙúäL7œ¡¬o@l®¶m’À³Üòèxì1@(> ;ÄÉ.ØïÍ5ë‹Ñÿæ€*[Äö&ßžÇÄíyÏóÓÝ0n'Oˆ™U`$¦uâ6÷ãÄdú˜Xg "¼<ÛK6 ÄT~a€¬ˆ*°À 褑”Ó+¹Àò„Øy!ªÃ‡¾LG:bŸk1 !ŸP޳ !OˆÇ£ˆ÷´ÃysaðÅ™¾G+€ºž§€=/N>[€ßObùìy1Ë/Ä¥&ôUáýµQòbÆ7ûÚÈuß›\ýd8£âb®´í’Îä‡A!&y‘?öYrDäHq) p ¿Ðo«E-RG‘c$ÿX)ºô1æÇKAöáþ<>>tW5zg¦áötäîl#,ÈÞ›k‚# ²0^›¬½>UwkºþÁ\tk©uk!öh&º>Ùšk¹=Z}ûhíýé†+#U7Ž…oÌDp›pe<¸¾}¸ÒŒ¿—+3µ­~žEp(Z.è¨ÕèÅGŒÊlŸ“í´2ÕŠ©$Ãå`·4‚®³”p–8ÍÐŽÇFƒÒT£瀅,º\¤. Õm-Ư ·l åâzŸ°1 ©qñ|¥Å}KHÙP!i®Q]œ gIC•¬Æ/ÀžF–Yf¦i%v]aØ/¬qs[‚²Ñ¶²'7R6d5.A¤\ j[ëJvù=ú¢ … i,Œu–õ·˜šCòv{}•´©NÝÖ6Ä¥²R ‰5›;[ìuò¶zSO³µÚÍ«õ @;-¥C-Ö¨±¯Q?³tÖ©‡ZJšMÝuêѨפ«Bu2+UÙªŒryFH—6Qì4»0Ù*L4 );ÌÒÃùaOµ¹°§\°€˜^þ¹çJ½~Äà / ¸¯”CnW[ŒïÖÚ¿>Û}ªI{*ª¹7âýåÚÀÝ!ï•Në>ç…VÓµç¸8Ñ6êd-†ä#æ1ŸðJ·g­Ù¶\o®fš²¶õ;QµÕT+e÷º¥C!Ý@¥ÚÎI±pRë-í ŽžØhç— Òuż²¼re!hÇÀ8TÊN éhnIŽKœíf¢( *‚쀙:Ó#Êv 2t”èÖ’”† ìqðÒa÷Kó {|’\8ƒv*5EA-¨SgfcjMŒ—FðOHG­P¢؃"4ۯȳñR½²¿*ßÀJTÓŒ¼Ãn² |"H‘1%DÆìÐ’¡;µyNuV™*ۥ˅nWf`Bô ôâT“üˆÇD±¨ÒͪL1{/—ºC\’  ïáPvñ‹÷JÙ‰JâÄrŠ‚v€GÂÙ'dí¶*sM’ }+w{vò?Š˜U¢4•8SÌK• Ó¥Ât>ç0–@¥ìãrˆ3Ì —™°)Èþœ^´x“ŸõYnÆ'9éŸ`ç•$©¤ÙuaQÖ'@}Ÿ±)؆Q° Ø#*9 ¦Ê¸IböA½<³T§•¦«…GTÜC5·p;èN‹sixÉ%¹Ÿê…©¥² «"˪ÌEFñ‘¢#ÿ %K™{c¥]­A|Øk*<Úåïv~lªÌ€•Z¦É¶ª2`”R:kU£Í¥“eHµ—A&Z¬.M·—MƬ-n^ÄÆ:Þã_¨®Ñ,uº|²#gÇW§k_^^_ëì I-‚ƒ!k‘_—k$„ÉŽ xØGöòxçͱšõÙ¦/.¾8ÛÿàDÛÙ±àƒS׿ë·Îu¿¾1úìÊð|éL·ÊRëÜœîƒßB+3RLš<£:ß^JwÙJ‚å’–°d‚± ›‹Ÿ)¯•)·3k˅း. ªp²Qòòê+$0lŒh•lrqlê§¾ ÚÍé(ƒ¥ÖËÃ=]}¹°²ŒUn¥ù-ň®5V«üVVOŸ€µ¸ÂZñrkì%±*eµ“´s:#VH­O©T Ål¯3¬¨ô4êÛª®Ù¥–Þ6k[“±±Nè› •QÀ/hk.íiw†«T‘ ª5¬V)ZkÕ“½Þµcá£í¶™^ÏT—s¸ÙÜP.j¯QE"·.Ï¥Êr)Òb>A{@XoeÔ˜ŠªôE+Ý§È êòÚ|›ø°‚±GË?àÑç—©3ŒÜ}!cnÌN›®×ô—•,ÖilÌFyF¿¹5žn³œˆên.vڞ̄M„ŒA·nÍÔüë½É—ËÑ“Úç‹SÕ+õš?ßšz¶Ð¼P­š ÊÇ<¼qx¼—;}£nA—™é¥ˆÈrb†âYŽ•ºÏÍMíôˆ{ #}? °¡.Þg¦µz¤àL‹K 2±’m¼4¿¢ÀÎO72“ôôD¤a&€Ç#Í-dD̬:½J]T£+¶²÷”õ&¦O’mã¤8ùižøÙ— ¥ÞkI2ÐÕT騠¤áRV…º©S”A.怋àâgÚG¬ü#fN²ŽuP[’àѰ‰Å }„S¸MÊJ°¨s”܃Z^"¾vU&Á“²#Nm–C›ãÒçHX{¬êt»6[+JÔH’9ÔOE%{TÂDnñ..e·˜u€OÛÇ.Ú©¤jÄ©EÿDËý½œ{ЬJ7*T:8}Ÿ¶—O í7*ó4â 17…Ë8È,N`Pö2©û °è‰\Öa~I259œè(B¶ g²lÆÁú+‰Ë<;*²ù;hyŸfþÂ¥íU‰RåüÃlên ¸u$`&(…) AЏ䠂w˜GÙ)¦ïPv©8‡t‚ÐN©,ŠÏTìÔØ”9fi†^”ª¦ÄG̲t+Á(Iá|Zç厴Û*l4bëJŸ×V‡}<è ­àŠ{çæ›–F*u:—‡+çú|³]- ËŸër/ôx›\ܰ…¡¥íÕ3ö†õSM–v¿/(Yéq<»Øq¢j¾ÃóptÌÝõúL§£§NUa¦à{®$ùK)UŽ’z¿°ÜLî.ÉÛáÐRbu&Ÿ[é•ú½"ÏSÆ8xC¡ÇDu‹Ð½ ó ·Z–:£¦åáàÒhhe2 ûE¸ûn³7” GZLË#ågçënœi;=®,cz,´úJq¤RÒF¤Ò…,¯Ü›,q[ƒª¡JÜÙdl­×–é ,Ú|«¡È^Zì*cº[)S%Ï©ðŠCiGƒÉ¥/Ôq®ŒTüÇûË~µöíÆìóßÝýpëØ÷çþðhéûkãï¯OÀøülÿë‹Ã_\ƒ¼½<òâÜÀ³Ó¸)îÞ:Õ½±Öù ²Úe€ûnܹ_zyeÙ7ׯ Àæ}ycâù…AbmçÂàë+#¯êâô7WF¡PD€Ð…AEg{žŸëEúÁWÛ·â§\^œï{Š©dµýõ¥Aè//ô#ûæò“½E­8´ ‡ýñµ¡·F¾¸4öÅÅñw޾97úúÜ0ŠÐ‡/. }íè77&¾¿Mþ#ƒYXâ@Eôмq.ê{I,­t¾$–„xjÅ„ŽžïI ƒ`›ÍmëKÍ÷šÇ7¶`‡äù™²ôe|UBÚQ B*(zI0Ò_<Éd£hq=Ž[°? VäÀH=o.¾¾€¯+`©—àœKCíÄ×âþ<'Û¿º~ôE|åhëTzˆ/ j!®ÇcšáLŽâQü½4¤þ1d3¾·HDìjEx–š‰W܉}Ò(²0’öube,öׂZˆƒ˜wg‰EžÅHæÞ\ØæöL™%VxH‚ h”…á€r»6[×gš6fšžÎ·¾\éÜœm¾;Qÿ`¾ùáJÛ¹±à\—#V)訖ԻK:ªeú¹owƒÆ¡Écàw¬Ö+¨-ÕWȦb·…Ñ\§'ü.…\܃ –•€UüfA,¥tTi­V7åUvÐÁ®qñ6&~¯@5ý-–‘¦ÖËG¶3¢‡â+-n$^¿c¨vqœú|Ÿ™ (j )‚2_)­©RÚÕ C—:‰·‰+-Ř´ùuÑÑ®òÑÎòX­¹£¡t¼ËÓßdœé÷ µZBNV—ÓQ¯ì(ëo³Ç"º*¯°Ò'l¬ÓFªUA¿¿{Íõæú*-€'V§o©V…}‚X•ìh§W «N«”@ªÊXu^>î=íÊì:;lgŽ6Y¦Zí=Uê7±MSc*®51j-ô™^&K7ŠS=Æ·¡P/LVo·q*¥©Óuê‰rñ©fËl…tÂ#8ÛRzªÙ¼Ú¤_¨S¬O†^-E¿;Û»5ºÔaYkÖß)ÿú\×—«­Ç*…/M×k~¹>þ`¬êl«}5b8Ñ÷—ÒÏÄìk ¥½¥Ìå:S‹¦("Í™ éºí¼*i€'¤,h)ãœâÌ ¾XKß_©§6Øùið¦Î\R¡¦€p¢¬J UG;håqг‘˜I€"·$v¯8Û)H¯Ö·»ÅAuQ_¥ºÉÆ­ÒP<â¬:£Å!Èó€@ 6Jkô4‡0½B]XkbB@5>y^µu6;…•Z ŒÐ¡uT”VjŠ —‰ÒMìd‹ UÏJî’Ó÷‰i{eÌs¿˜¹ODß«æ'ë¥ibæ^à`™"Õ¥Ï2ˆM’$«"EV²KÌØæ-Íó[ ÌŠp¿^–$çïµé3tÒd û€˜y@Ì:È¡ìfìÐK2,š\u§Œ{ФÌ4©Òåüu^©*× ÈöÛ9VE'Ëfî*)N ì.ÊÙ^œ·‹^°§¸`7­`/½pŸF‘'`óèT*iŽD&ä¦SöC¬ü$ÊE™jY®T˜.fh¹BV""d•Û¨EGd¼dXTÂT)7‰´“/y1ð©»t¢T“,Tb¤ÉÙ‘*8‰Ä*«4]ÂL u s_©”Ü=×ß’ëx‰ãíöSSu“Ž+K±Ó“u§ÇkÇ[¬FN¢š¾§L|DMÛ2Pæ:\#µšûÇc—'C7fë'£ú&;M”ûO:æž['bg#Se>}n©,Å(:ì7•©³½†¢XHe–¤DéÑJõP«×¢. ” †ºüM5zŸÝ1·5ÊËX+è)©õr›½Âé6W¬\æÑUØY5aG½ñØ@ £V;ÙéžëóÜ\m=5Yyv.tùD¤±’ë³PðÃRéÂm=Vµ7ªkËÙ+3Õ!³>ÈïnÑwFõíö±^î_ªÜ‚º*…ÕLµš(5AYm•J¯ÎÃZÀ+l¬ÖxJé²’a/ïÑå?½=õË‹å-^|¼Úuo¿É1iNvâ‡xÔùþîìwwf à&ñå¹7—FþÛrÍËKÃo®Ž}q}âñ™Þqby}eÆÕN¿¹7{9vuªîÜhðútýÖé=9Ó·~¼ Î…ZÈ’Ñ  ûš8D1@Ì•À†S¸'æܶc&¬AN[p í˜M ³&LëK˜)¢p~q¶ï)±uò—òœØmiÃLýò iþqª‰3F×GÀˆÅ_às=ê¾»8ä@ÂéPãÞ Y…Œ‰"è‹P¤aÑPä[ã…m½!ÖaºþŠpº>v‰„%²ˆžø Õ xº@;äXÐi!×…ˆÕžÄr n¨HÐÎpÚð›ó#ï.޽¿2ñÕ•©÷—'¿¸8ñöÂ(pÃ|eôÃõ‰îLÿxwæ»[SÀ ­“ÌF át²è-ùYàC!?\v2%1ó8)$¨À'±ÍøB ,ˆ#@˜Ê›ði’7êW#Ž4䇈ŽòSFäFBHàAX’ȶH;ù]ÚüÏE¤çÄ–V/Rò»_Pê#yƒ/ ‰Ù[ò F É`d²“$ù#E-b h%†± F¶òŸ_Ëæ8ó´ñ‘%¿Þd)äc1$Éàåî\€ž²”ÒBúÜ›mº?MüëÉõcÇ€=Ñ‹}å[Ç;pÏrf´²ÁÅ Z‹¬ªT§!WÁݯ%Ë9 ~kq´V^ãÄjÕƒ­¶X¦ÊÃû>>!¤²ŒÓR†Á’zŸ@RegùK‹š¼r+½Á/j®’÷6» a¯– ,cáFtÔZ£îo¶Özµn>@¨ÒÆŠU«½Fjƒ_<³Gü¢J+³±\*+®´Ö©‚eŒ¦ !¤/¢hÒLj›]ë,Ad„êi´vE¬íõÖî¨íh§ûh—c¢Ç5ÞíìnÐ ¶YÆB}-–ávÇh§«É«Õ ´9Gz|­c_êøeM5ÚŽ3FZac”—R›¾°:`ʳÊRC6zÀB‡Ý_J/·ÐkÜ€‘õ‰[|¢*ݯÊ+å6±“ª€=†_G±ˆÓìª\Ц- }·_U«Ïm)¥.5™&ü¢ó1ûJ­fÒ/˜¯’ ØËõꥰêÖ€çõrô»s=w‡|·ú]çÛL¯—ÿùöÑŸ/÷/×Êß®4Ãáb«õÇKƒVÝ ^épœi)­>^§;×lë6ЦüÒFYN›2Òö–ñú|¢Á€¢NGé (í¼=ý€W‘ë”dÕZX•šâf§(¤§C ¨)aV¹ªŠŽ8ÃLm*ÖšX0Zãç–a1ÒU…­Na¬LP.˘YH+•e*5¥ÙÆ«Pä7ZØQ+nUj ±’c)á ­1Òë­ìF;Y+©J‡ ƒZ*ñjy®GšcãqI³ë¬%!#ŠM”®¦ïðè¸ÉBÊ.-ÿ°VpXHÛÃ-ÚÁ*ÜFÏý„S¸MÍÝkXó«Ê(5.š×˜­ìõ•æV¹‹† ƒ,ѤH2(“Ôâ‡9KT²Ó O×I2¼ÃBzDÉ?¬à'xlº<½,MÀØÅ¥mq1 w<&š‚I†phXÔ}Œ¢½|f—žHÍÝYœ½‹š»»„v ¸p¿$YÄKá@QJ³¥oøìdd…Ü(¬âý(å2ãÀ“¬§i$é¥0UÌN$^$(Ë4«ó¢Ø ú `îWò“4Jî!³"Ëc.¶ªrðEòÁÈÿ˜å™Èj…)^s±E™-/ÙgQfÔz¸Õn¶ÏLؘåiu^^•“Ua£Ä‡ÝƧ.×®ÎÆýBgD 81 “«Ë˜vEÆpÔ¼6Q;Üd>y´úäHÕX´´ÞÎZèñ® úé6G…&w¹³ìO?>Óýêòð·ë³³m¥ãMÆ“ÃÙn×p£Á­Ê2 ù…!«L•íÔätÖiýæb½0­¡\6Úé‹”+«ÂXy¸ÓoV5VkûÖÍ x8^®ßÎmwŽ´8bUê°Wì6Ó4¢4·±¸¹J9Ýã-ôyÎOWµhÎÌV^?ÕXQ–§—®ó «}üú ´Óшß%ÆÜx Z+i¨ÀRWÁù¸‘ 7YÕ^‘A•'¥™4yµAy¸J©‘gšuEN+Ëf¦)DiJba-!äd\?Þôør×½ãMï®âw~øÖtÃñÇ©^ï©ðõɺóÕø-ó|u}â듯Î=Çyaø«¸þòâ0äÅ…¡g˜1¿_zuixýx;äÑj'€çûõ¤_Ý:·«ÇÂçÿ_®ÞÂ;Ž$KôþO¾³ïí{Û;Ðlw[&1s•JÅÌÌ̤âR1IUb -0ÈLm˲$[–¡í†™é™ÚáÝ=ßÎw³²Ç»ç;çž87oDFDF¥"~q#"5êÜ4_v<‚!òæ(Ïñ•"„›ÙgÈfžâÑå WWQÏÀ»©4\Â`„ìR¸ùr§ôæÚPy"‡‘ õüO—Èk ¯-€7x€—¾¹5 9Ãå¨- £ÈÀ ä‚(-¤÷‘å'ĈðLYGÁfù\pùò[1®ÄÑO €ù€q¯|Ž`ãË+E w7®&!7ËtôßHS†"¤ 2G•Ê. ôk„—Š(áÀ]ïn)Rš-”µ‡,i…ËŽ&ÄÔ„>r¨|5‰–ˆ~¶¥|ª:ñî¹Ê^ ÄórÀ¯øêR Áž«ã ¯/€åèb¶ìb*~um€çw» ¿¼7ýÛGóPI(rF‹@ŸyÞKùƒµ$êÙ)+ Ôóô"ÐÈ;æAùШÿéâ@o,;:€CR¨’¡@ûÙrh‘ ‡fŽZÐËÀR͵”ߊÁ2è&ßÕõüü£H…!å”LÐ @ô=D B«ñ.“ÿßS€™€òp!„šàþluø %¢5/CZµÿOçÏÞreQqì̇APàùoçÏBŒw§ý÷¦ügBOúŸ/§ŽWÒ‡sý€=ûóÈœeo-5{4~+Þ¤j5ªÛõê.£#áÕ›õ=ɨ$àdØô8è=Ji­×B±«{l ˆ^Øjµ „¥¥~Å@T±3|&’IÒ®á7*Ùuv&ãÆÜì”——CVd+l£CJ‡ªÇ©éuj>#Ù,ívªq »´ø\@j“w[¥˜ˆéRú´„”‹o•t@×Ýï¢ûMø¸›æRu$]´‘˜$ÛÇ‹Ë/ÍËèƒKKtjˆ>+Ûkf¤ƒ¢ ¼ßLÌ„„ù¨d$­jŠ8ÙŸj(©èWMä-sC®É‚u$k ¸A¯8äì,èTcBfÒ@H0™V8å­Rêi‡ªžKÍk±ëôâ6ù«ëS ³ÁÈkÑ1ô¬z9± Fj-½Þ%ÇZ%]Z^“†×£ŸZÅ%V¸åmQmwÞD\ŠHóÊîÕ€hÁÉTt>™ð,zØ÷‡­·‹ÆÃ¹¾ßßúËý‰ãyßÏ·Wâ¢_]NÿýùòwÛÉÍïÕB`3Ä?œpþîFéåœÿõRôɈc#(pv²âá¬õ‰|ÄóaZ]FÔ‘t,öIŠzÊrD9deû„íS!…ž^'ÇŸñH»mÂv³NŒ9ÑRú¤8§ Ó)ì22›LÈzVµ°ë”™Ý »UK©30ÝbŒG‚•a* 6ΘOêâµkçx€j‚Ò=©ÚD«s°[ÌôzŸ¸;¥§iˆ•> ÆÎk³rZÜâ. ‚¤=§Á¨§ÕXØÍ }R¬_ŽÎqŠ;mü6¨›Ý¨¤Õ §¥äóÄ–Ÿ²{OñHgz[~ÒQ÷›«þ „G®P°Ï¨Ù§mÊ&“´Ö¥k·ÈëèØË9:I —ò‘~RÂþŒMþˆˆùý û£–êj«ù¿] ?êmûÔõ)wŠÞûó w‚Ôý±ë}á„×€iû »í}>³Ž€ý ÆG…¨M®ÆwžìjüÚs†¯Äµ vTP{Îõ´Ÿèlý¨sª»í“šóÿ§½ùC<¶¢¥þ'`Áu!¡§¢YÒú¸¨£åC¥ P ×þ„,ÒYá4¾ó㮦Ã%è@8=mÈç”qm¶×¿×Ñð¯LB…€^IÆ|LÃ…K9#dTˆYð€g@ þøŽؤ 9¯ø‡Ý{Òg"&¼þ39 ñö€BÇ~¬âÕ(§5ü³¼Ý¦ìòzœš°6•3¤¼\\ã{Ã1EÈD¢´ý$îdÜßÈßžŒ«Ö‡]—§C!ÉpHBk|/m"¼Ú|v)ûêFéê„ëájìûÕ¥‚amغ=åÎz9 ú9³¸ÕÀoT°j`²à1”œ†–³ÿ$¡Õdò©¼Ý­¡jø¥´Ù(ǜܰ›g3l&‚ZÑî0G½VÎ,Ç•RÆ\TtpÃNŽS…›LêÇ¢ŠûíåÐtZ´6®_UeÕ0)ƒÙ–ÇHLù^+!घ4m.fGýŒ›fRµ» 8è©ÔóF9F%ê°èIɬÆéäœ q‡ZŽqX™RQ‡IGdþé{n줶_^/½¾Zº3í_Nª×Òºí3È¥¢u«`º1æ~¸-U†ñF“Á7×Gžo<¿R<¾Z:º<Äòäbd#³U²N…Es½`úöþô7÷¦lvÆÝ·gƒ÷#7¦ú6‹–k¸ë«;“€C ϶ p#âá¹R|viäÅN ™ÔÃh‚ðÌÀ1ÌôËSþ¯nŽÜw?\ƒýYé„Áþ±fßÛ¹ãò¼B4 \‚!úåP å«KÇ[¹£ (¨0 Œãègßž”?Z cúîb¤ì{I£P ½eo©È­<ôÇÁŽæ éÁò!“8²q¶ —+u¼û¬Ü3ÄÛ“FË…QËë˃ÏËä $@åT†’Ì“2GAn{å“È`y\¦/4 Z.â@(WòÅvÙ^»’@3y¹]xsuèhÊ‚ —¶®Œ¼ÜzŽœOÏ¡Íò‹;“¿}´ðËûÓô,Ce˜Ç ÈñÙ„œ:*ÃBåd £Éþû5øÇ;€º†œ¨*}}sô-²8…”øŽŽÐ<Ñl¡4 ”Ý¥(Z(JP(á„€:‚1±‡‹a$órkü" ¯ÙcÄé”/{«‡ð²­$wyàr¡)â€pÊïµ<˜ =¾{¶’9\ˆíÏE!|¶œúr»ÂYÕ¯évéº`~d7c‚ÝAÑëq E§É€‹„…']Ì«WŠZüvºßB‹¹9ýNàMŸo·']œÑ´®”R†í4èd _5+: k÷qbn–×$Ó!ô´e'tËŘÂUþ~`Â#ʇ¹€"”'Ý¢°¢ßÊX˜v)Î!ïqÊ{½*BÄÄ2 Úb.fÚÏNxè¥(ß­j‹;H£Q!òõ¿´ruÄ17`Jû17?TbúÁ„¾”PŠ€rРb…~i²_Š+s!i6(Iõ‰2iÚ/ñèÉ&I—[G±éV#Ímbø¬ŒD:oÜž î,„«‚FœEÒâÖb4¼F)£šO9'¢Õ(˜ bJµ[HF²KzìŒEØíVôj˜Í%jîPàœ>­Ú Üû”ßý¾‡_w1£³P·ª›ÓÍAÓon^ÍiM»!Ü›qÿòfñ×7K_o&¾½Õ´í ÿzø`̼ì¦ÞHHl¤ï6S¸9ôtÂùízâgk‰˜4Í­Ûô K2Œ[¦Õ®ú$ÓVÎrP1jeͤI%> îœ *”Äóv~[Ô@Ñ1ëå¤ó´ú÷Õä­Þ#éq ºä½ç•Äj¦BŠ;kbµèp©ÀW‚ \Ânqçg)-cb(p§Õøs1 ©_MÌš™@>À9†½{DaÎÊn²±[L¬&ãwƒ¢¡@Yµp‰|ÕPÐ!Åæwž”÷žU‘ª¬ÜV¯MK«‘ω{Okèµn³‚VÃÇUð §Ø“Ðæ Œï:?¢àNí|ò'bêG N ÷# ÿœKÛ̧~¬—TšU bÖ 9¯BÄü”Ï8AÆý ðS9ÿ<±ë#` ZÏçTìguïaš~‚k{¿ñüÿv*îcá$t†€=Ç|*ãµ°H癈“çŒIÕË œo©y¯·ãwß~‚‚9Í"V3É5$Ü¡BlÛ§tr5ƒT]uæµÖÿÛq‚Š?Ïe4ð™MÈj¡’€9%ã¶Pq§0-`Z~ŠkÿŠpðŸR°Ÿa[>ìlüû9_ÑÓö0^6Nmoû üçy—^Ö!åÔQ{NP{N²ˆ€CLÂ)³FÎk2ªAg÷~®·˜e@>q/WƪÌÖ ›¸„ 5¯tö»ë·µ‚f­ 1d¥èø… È&kO{¹NyWØDŽÊÆâªëóÑ…¼éèÆØ³k£ËÖ;ˉ‘€èÉföÛûS{©ëÓÞ•¼þøÆÈÛûÓ¿>ZSQÎú¸y/GJ®Ðs¤dÑŸ I¬JlWÍ{.i2o]…íÜlP‘ôIã~ñ`BòpâAaÄÏ“ êœ&b>¢pé(V£MGJ†dvQ-lë3ãvVÜÎX.šî¯ÇWF ËcºùÕHF\J*Cv¦AÚî·PŒs.>ÜÇp˜°ñ ;àø°Œåtð‡4hQcùÔj§žìД‚&£¢ Ä¢íáЪŒZ‚˜×ªWá€B§LþÙõÇÛ‰W·^?ZŒ\u¬guëYýbL1‘]Ì.Ä•À?—‹6`¡½•ÄÑL–Ko® {{®½½6öåµ±ãË¥§Û‡[…Ãí¹¸ÊÄ®§Ô½§¥W_žôß_Ë@ófœl·¬=i!g­´°S°ÒwF]/¯”¾º5þêêГ‹™‡0‡…Ñíbd+‡ ÔåA( Jùr†ª!P ÜoïLÞ› @¾]x{}ôÙPMáå•âs,.^\}ÂçÈJŒ¤™åÙ=Â9ˆ#.3G« Êè’{y©ðê2²¾úÛkÃÇ[yÔÇ‚|á Y¸É<^Œ¾D6Û¤÷–b CˆÚ!“'ËqH wŽ:g@ qÑlæP0€œáv¸ûã È÷rÑ{Qo „xsµôY"AR>š?˜ €‰ßùmÐôˆ¿h- QPU”pД €r€ØÝ…$ûù‰ïîN½Ý*œ@¶hå* >YN¼ØÚB‹¯‘…­1øAAAøçr*•„ô_]ùþ‹9ÈçO‡kð¤ûÈ’Væ]B @à h^häýµÄÞJ ËÀ‚¸ìP¿„ ð²=.CÜ…þX‡ÈNõ$C&ôçK™£rÇe  ¾@Ö§ÈAÿàZï#KB)4 ò‡Ë—Èñ®ZHðYtË€²‡œË<ÛÊ ÛŠž#¤ØÑZAqBô^ôøcçùfg~(wÅËÂÃ9d_1\]@ÛyH€œ_ R%€ÈçézòiyÝ ~”‡óAdƒPùG„&Ý[Š1>º˜;X‚?^Ɔß=µ¿’„(ÐÁøhn ƒ<ž‹>YDŒ÷çBëÐÎ…‡ Ñí‚y-¥»=ÞÛƒÖ~m¯CÜ’p1’>ŽÏN’‹ëíVB,.Õj±I‹RÞî÷r¢A¡Aµêp>d›1zÔR\5WÌd§²{,¥É„„©°¨Æmè Ø)ýÖä€q0.Oø¹~%ˆl/¤˜Õ]v &ÞÇó™i…ˆ:å“&¼â¸Gw C6ÖP\çÖ‡bÚ¨ëR“‚†OKñë¨)f*o*Åeq³—øt=Y{*­ Ú1;Ý$j1IÚ ùxÞRŠë§ Ž€>7dŸq%ý‚˜2åY˜¤_up-2¬UŽˆj¦ è„-²žˆƒç·qCn!Ì.F½k“þ­¹ðBÑ·Ó=ªn—ª žÑ¡BN–Á¤U½:³Ž‹?ÇÆž6ŠºÍÂn¥VAkÐsÛ¡¶Úe8“cW”©Ò©§©•:n-³å=·°þÉJt3­ÚŠËî”Ìo.&–"·‹¦_ßûÙÎÀŸv§ÿ}oî—W ¿¼’ÿÝõü¬ ûlÒþ÷«ÿv½p0fûb@·7bþã­Òno§•o—#½3¾[4_°S®D¤ËNî¢}9¬x}!¹Q]JèÖ¢Ê9¿dÆ/)Ú9£}nËG^)& &ˆ°Vn»Š\£"ÕX8­¥>YÊÂÑÓÝb¬K„t²ó:<¬SØ.ryEX+«ÙÉmMé)EG®¶sZ@àþqñÚ<‚¿Qâu¤ª¸Ž3³!¢":¸m~)6ª&ùe=fv³ÑRuÔZ;¯MŽ?¨cã¶ Eˆ=%Àž÷V{‘cYÌŽOx¸S"âYîs§ƒµ‚]¯æ7‰éÈ:Ž˜Uc’·ëD5jÞi›²^Æù\Ã?£œÒ>ÎEÀ9ìÿ!tþ/!ûð#"öÿ˜5- n-ŸrV#háÓð£ç¤‚[Oh{Ÿ†ý˜ØñSö#!í´˜^CÃ|†mú‰ßÆê÷ -ê^¤K̪ÐjŒ:.µšA8Ë¡T³ÉU˜öº›?ìíú¬µöGíïSzN‹9-ÓŠ;õ²N)»‘C>KÅ~®6sÉ•\Ê9¯™G=/ UƒEÅiT°ë4`!–2ï*HèÕJN=Xø¤3Ѐ:Q »·ÂRLö ÇSš‰´Æ.í°IÚT&bªá°t}ص4h Šï.'¿?ܸ:ØrMÅÔ³IíÝ ±«“¾ï×w7²þþÅöñͱ?½Ù¹6‰è‰Fv½{RŒ¯‘ÎH‰§UŒš¨“Ýga(U2N“YŽIô rÙ…1ïDÞ„ì½Ñ`Ò!¾×ŒWòª-ª6½¤9–ÄýB ¿Ñi¥ˆn;Õ¨Á@—²QS}ìá˜lkÆ;œ”l-öMªgFÌ!›Ös ZƪÁÃÉ Øk!þ¥Bbƒ¼3æãG¼l¿ƒšŽˆ@Qpêíª)³Z#hté±ICÈM™(Æ^³«•võYX2f½†Û´0`Ù™ñݹz°º5å¾6n¿>áÚ4]ˆ«Ö2ú›“þµŒq*(™È!¼6ê¾=å?‚ôfá«Ë#ûsñãåüëíáçÅWW'7KW&B;³Ñˆ™ÉÂT;NpÉ5 A‡^‰·ëI”î9øw–“;S~§y#cz¾žÿjgìxsðÅ•¡ãáÃíÁçÀQ×Goäž].½¾6úe™¦ öùvñh I—sOÖ2Ï6@{cX ôãK¥ƒõôË2‰Aøâ2àMzûÇKñç[À90žÂÜ@¨!¤„‰öó,º¥ä¸¼¶|ù‡íèÉëû³þG‹ad²Ø,²—wn óqPÊgŠ‘“8Çå]µètô9šÏÁzòÑ…Œ†Ç—ò‡‘Ŧ_ïß\+Aˆì)¯w—VTÙÌ=CöÛÀè–@åÕåÁ/‘¥®ÜÞ…~ijžFÓ¬&_^€§€Ä`yЬĽ~qwòÛ[ãßÝ›úùIÈq%•© nA¼Le>9,{™Ð| ˆ£ÜóüW×Ǿ¾1þöÚÈ›øMÔ›`°H ÙþçËKyvñß7!ýîb …ŠAhæèbâ+ä U¾|Þ<…6ÚÑ?6V=-9mC¤õ UדåíÍÐ,÷”w¿@[AË À°‘†+“jbÑ 6æÞ,²¬ ·@„ëÉw›¸PåùÒ\û+)ä Òë@MIƒu$Ûç—ron ½ºVÜ»{¼Ò¿¿™Ü¿¿7ïCöü5Aƒ”!êe™»€LîÍ/õï—N¨<\ŒÜžô>»-ŸF‹ýùzöéJúÉ…øî\do¡”Çb¢»‹H5àU<Þ.n䟴,—W¾–"{» >Ÿ\>¹~r!rïðæÀþ…Ô1ßÑRêp1þx:üÅdðþ„ÿñ|ò0|²;¾˜Û_Œ?]JAš'Ó‘g‹‰Ý™È“ÌýµÄbN—qÒ½òN ¿Ñ§Â€1‹!ñTV?‘Ñe‚B«º[#nq I³BÜà°Bf6&OG¥N)êå-Œ{ÃnvŸ™œò û½lè²b^V)¥ÈŤ.3!@3’×AWØÓ«tPà·’ÁÝŽÛLˆxÙ~I6*/اJÞÁ~­YÖcS÷†ì쉼9`¥{ ¤lXêÐVfÀÂÖ ;ývÀÂÏY’>¡[OôšH¥¸f$­Mz ¤¼‚ˆâKÓ² «`>u"k,8"¡MCöšAßgå켄_žðJ’žbÌ0’¶­ÜÁ~=(i¿"RÎì %÷ê„o&gÉùÄÙ>¡GójðncÒËÓð›xÄ3 M+jUp¤Œ:!¹JÁn4‰» Â)­Ä&ïÑp[4œfƒ¸SÅ1¨Y'îPó[Lâqï)F㿬¤’áΰù«Íäon–þýÑôËåðѼÿ?ž\øí±o·Òß]ν˜óýõÞèo®æŽç¼{0þ'ÿroôgÉ{È·¿¿^ún+ý_{ó?ßHåþ€þj¿dJ¿•Öl…DÇó‘ã…þùÈÁBôö°g;k¹1æïG¾¢\Û¯§YÍ&v‹]ÐÅjùÄ#é iÈ}rü_ávMv~§ _Q‘,Ìf¯ëb¤˜3vvOؕ֓ǽ¢’“×ú„íJì©° ãâµx…íaÎ'íŽëI“ayÆÂàA¶1³šíì–°ïâµÛ9­)#cÐ)ðI°qÕ+ÅBeú8¹ÚÂkÕ±ÄÄ3A4¸Ž× fÕYåVy·EÚÎÆ}. U(Xµ2Vµ˜V©5ÃPŽ|è›U¡W‡½<*Ë?‹ÙŸ“1ïÑ{¬WAÈ¥|DíùW6éC9ï4û¯lâG2ö9 †Òõ>tÒ nÓ+¸¤lÂ'4Ìû¬Þ,НšÒù)ü¬ VcŸ‰„—JG3j{Z> uŸ(ï´i2Eš”’™°†A8 Â&W  "V3P ° ±ûTWÓ ÀB0@Èl‚X०³ñ}ZïY«+þ‰„9ä™  d"d60 çØÄsÌg@8ŒÞ ñ,¾ý#!½Fyé<ð˜RЪ¶›”=JN£ŒY«d7°p§@„”JZ÷§ ÌIfÏçÀ?øÃ1„äó ­ Ÿ‘´4äüþÅööt` Ò2k„¸Ï ¬ºK“þ;Ë ¯ _r.$õ×§‚K#¯óc-­*¤#|ýpñáFþÕÝ™_¬?Þx}oöùñɸÊ.lµ [âfê@Ÿòé®úßPH­Ú‡¿—˜Ù8žÓÇܬ\HèÔÁ ¢-` Æ#Iéì áõþÊ숓M;¯”µÉÅÍ^'C§ìJN=ÎkÀ-ŽØf‹¦ù!Ë܈yfÄ” =fВߦ—b 2lÐÉI¤C½]‡$Žx8™ˆÔ¨ì³«d¼Zã¼’Û`·™åP´ÝÐî£fäI+ëЈ:<š\-&U•ÂòÕ!çùàƒ•Ð΄mkÈ|eÌqyÔ¹V0®dÍÛ%×vɳ–·¯æ,3å©ÀãµÌÛÛooŒ¾Þ*Í:…ÃFæ“ÅÔ¯¿XýöÎÂãµ¥¼3fãitÜY"ö4±÷<žNo ;8̪… ×LÁ´T°Ü®¾¿»ø«Û³ça²œr1÷x=»iðàrñèÊÐ×÷f÷× Ï¶Š/¯Œ¾¾6òòê(è/.€r¸1ðd5wp±ðtsÂr²áG` Ì ñ m Øsi,av\žù‚”½•Œ;Ï.fÞz´g¯|˜ݱ¿š@OР($ ºUÝãŠnvEF.Ä‘FÏàÀ-…è/˾&ô˜à ²±v3óê*T)4„¨À°þƒ¬ü·„™@ aʸb¶O â\B¼C嚯&!Ä k[½@øh!‚Ò Ê77Ǿ»7ýÍÑŸ![Žgqw å–²ó!º†Ð|ÀþfgòË×7Fa^_)"›«%Ägu1é·¡¿y´ð»Çþö|ÒCqÊ‚ØÝÅh™µò¨ Lˆ´‚ï¶µ$Þírý¨¼ð´_6>-/ë@›<-oNþêÆÈ›ëÈ4ΰè•H¹‡Ü‡d_^+•I YŠ*7 $ šB>›¸<Ú‚–ÔÉ>ß„¦Ë£ó´ìÙ{²–xHv)»»Öÿh5z¸>ÜNî®DU¤r²'ÐþÈŽ/ø½²{+ñsÁ=Ä]“„QV»KÇ‹ý úwáw‰?8™ï4po!†\ü,ÆvË>™ýÕÔÞrêÑRâ‹…èäûÑÕØÓµþÕЋøËÍäóõÄÁRìp9ñ|=÷l5··ØíÏõ?½8ZÉ<[Í 4µœ¸?|8~¾š}ºœz±š=œ?õ?í4~ycdcÒ6ú´Ý~C¯ q\t…>#1d¥úL䀅 “èã» D‹²[Æ«v1ù¸$$B€‹ºÇogôYi=!äb $•Ã9m1­LØ2ìe%ÃÂ|Byb~nÔÍŒyÙA º¬dHXHÈã>ôTQ§ßÏ8¸@ ±>±×Ìtèˆóü˜{ ¡ 8^ ŦícØ%:yÑ(ïõ˜ ¿æÝv-1âäÄÔýn¾ßÂpi‰a;;æä¥¼¢BP¶²œÊ^¯ŽœôÉ#nQÐ)è³°MJ‚VŒq™ù~­j( ÓkfÄ=¢B@6_°%Ü”“WH”Ì:)¥*áäÚ£¸Ão¢Â€¢¶Ì4>ù³÷”ŠÛÌ%œe`>‡‰¡š×¢ä4[äX·Ž$¤TÉ™ 0hò‰ç¤J ½@&5A‚9‘Ð`V’;ÃÆ_\ÉüüRâíJðx!p0íþëÃÙ?ÜøãíÑ0ù«­Ì=šyµx»ýûáònÿújá/÷Æ¿¿Rx¹x1çG/xŽg}7’ ‹~ΦŸw+¥Z÷qî”ìÇܯ×sfƒ÷'ƒf£}‚Nzý½"¬0FŠÏ9E2b•[†óHqfN[ÚÆ³ò:Pà±±ÛÂJ’_ÚTõ¤:#¥!oáÙXÍ1%!¡&ÅÝq%>¡!ÆTx¿5¢ìªð>qWÒHMÈQ a4 ò‹~ÂJ‚…ÙaœÜ6P<ÂnàŸ>1Æ%è4³›u´:#«ÉdÈlో:¤”J½°Ù©Æª¹õBÒi1õˆŒQ¥âÔ)˜Õ Úy£¸XHÊ8Ç'ª“V«E•LâûòGFEUÓ,aŸ pØ ÿLÃýˆÿ‰EÙÈ%|¬äœR>Ó ªÍ²&1플yt%û­ûÇôîŸÚßãôž‘Òë dãÏ0p§(]'Z*ÿ7¦é§½m±ÉçŒZZoY+ä4èd8«–bRÌj¢RØ)f7Óñçg˜Ä*¡’Eª€t£QÊm±š¤Ü69¿£ Hp£JÔÅ¥ÕA¸—Ðõ9ÜÀÃ~f7IÙ ^‹œÛ äãV)ù­\J‹pöPÒH9%gÕKµ:a›ŠÛïªYÚe’vZä ñ,Ÿthø-Y·^ÔNïþTÃiÜYˆÎd ëcžA¿ÈÀiгëKAÉ|Öxc¡ÿW½‚¼‹»3úêî\ÒBgµ~à”t\(XâÚÕ™Ð/öמ]ýÕÓ_m•bnçÇnYWÁÃ{óf þ jÒ ½ŠGªRÏÆÜÌ©‚~(!]›r_Y NäTé>úxZ6•“£üá¸xaÄ•ðK”Â6›‘h·½.¦\Ôõrá)ÌÒö™¼1áb$a¦£ž)™=dòëìˆÉocÙ4„|L©“v&50ŸZ™ ÎŒØ`†)}vªJج¶Æ|‚þ>^ÀExèn'ÙlÀð¹U^˨Á[µ$1³Q+h/ö«ÆÚ‰¤úÙõ⽕è¹ÐÝ¥þ;ë%çXHUp Ö‹ÞÍaÿÖˆ6a\.دL„®çî¤r)™7R%ퟸy­#}RˆZñ†Lt‹¼WÆmáÐ긬F·•Ál «‰”ªXL13ávë ‹EÇÑÎÄó푟ßY<Ú(~yuü›[3›ƒO·JÏwF÷7Ÿ]~qeäù% –¡——‡_] ò³ÂþjöùV Hp´9úÁZÀf- „òl3|i0¾¸\|{cìËk#(óì²+)6zcùÙ³¨SZÂ# ¨«ÅcdÃüL©Ý¥´´ DA¦‚‚,0A›È ÜÉ^Cå· («€ý9*¹²—¦p oÈöàñÖÀ³ øAshìãåºYhuµý¸¢ F¤>#–yЬp%.FÀ²ì&Šƒ ënÈo ïÛÞj }ßॾ9\Ëìç ²”€4‘}8  ñ/æc»à¹;xˆ|¡øÞ"à¢àÞRhw>xÚwÚ¿;ß¿¿ïR ÍêΤÿÊcgÄuw&Y½¼:„€ýZæ`)y¼ž¶˜x4x:ÓÿÅTèøÚðòˆ5dÆÇÔ¬sÐr~^!$„Î$d¥…ì ¿… ´SˆÊcÈÇZ„Ié@Rñ2ã~A*$qIÀ„Ù~ÈJ™ŽˆA~ÈÍ‚rCW &EÜ„_jÕà!ÏÉ¢u´`‚þ ŠK‡¥n#Õ¡£ôYXz).ä„\ü ƒF£k×à}:ÐNÄÅ:¹É>qÂ-Œ»ø)¯Bèd|Z&$ƒþ3ìæù¬L™c2(LèF²æB\3?Ú73äÎEU`‡Ø‰‚u±è\(Ø >IPO1 ÚôZ«´Ëƒ|—¬Ã,íÔ Za4ñèI~3K:ËÆWhømjn »÷4—pNB«5I»(RF½MÙëÑS óQ«!B«ã–v+ñVÊ|ˆ·èg}1b>˜qͺ߬Å^.…þãñâ_Íý}éï‡K¾=ú÷½ù__)¼˜üùîø_îMüéîØßLýÛÍá×BßmgxþóÑÌîÎÂåë¥0${0h¼ÖìÄdÞNRq;oºS´¾¾˜¿3ê~{uôíõ‰+Œ¢?2Ò-œ6¾Ú§ 9­^Þ-ÅY-qËÊë„(¯ÓÉët 11-­_C“aκ¸“AZ‘õ&ÔŸ¨3k¢¬¬¼…×’ÂÊÞòQÁÆŒ…1â—$Ì´‚›7àá[yÍY¯ ìD0ÑÝ®¬…—AAA¨d¶~Ìéø”Ûý™œ\eâ¶èXVI'sRH>£á6È™Õ<Â)Nïg*X5@A:A“¶¼ø.y¤ôsc·ÛÐÁ&| ¢Ÿ4Êê,ªF·±“GùX#ª’q*¬ê&³¢íS)ëTÈÑ+¢~Ã#~ªáU% 2æ ¿Ú,m”2àü„Žù)„vU¯QÜ%c6$]*^ ‡xŽC®”qš3x´j!³ŽE:Ç$žåЫÙÔj î —Zô¡Ԃ¢‘`µÒƒ¢à¨FÌi£BÐ!d6¢É:ßÇ´|ŒïüŒ„9T€Ä§7€ÿ€Bé9 yB wšK©Ðj$ìF1³ª!bÔ©mÀ6]?†Kéöåý¹ÿ÷»8í`%m ¿ùÁzöËûó#YD¨Psê”Ìj9ý¼‚Q™pÑWÇÓyõꨵÔ/œÎª–GŒóƒêÙ‚j"#àèW ¥ © Ì À 9u"N=—V ÝŽ[‹)Ù>¾SÞw²Æ3º˜Ô¢Áð¤CrHó‰ýv¶NÚQLiS!ñHÎóóRaQ)£ {XN#Þ èP šÔ⎰Gè²R:œÝBp;© *e‡VÕ£Wõ:Í,øá„ôz¸Í"ë˜0Çd¥x"¦šNF"ªŒS3s2NIѯÚ ïÌ&rŽÙŒi¥èšè×l{‚ Œ–R5—ÒÏ¥vi—OG‚¦† Þ¥ŠÉ¨;ØÜ2½žÂ¨ëOjÔjŒ„ߘò‰n-¥¿º·pxfëÃO/__›x´’9º<òæÖÌÓíÒË«cÇ—GfŽ/s^^)½FÜ;¥§ 0Äá‡,œ­¦0»XûKÅ;óá…¤:n$ôë{‡ýü•¼ñãváá…Ø‰¾½ÅøáR ñÍÇo”\«ƒæ‘˜4æ äýܬŸç7ôF¬”~­Ï@(Å”£}6$É…¥Å¸ªÐ¯ÌŹ˜¤ö$‚¼bJSˆ«Ü&ŠVÒ1=äÌõ+a¢ð` A&*Èö‹üNZÄ˶j±ý>ÎpNç4öÚÔ˜¸°SýV ô?™¨ £}œdPì2ÒœzÀÁEœ6:¼COÍ'ŠÖÁ¤z­¸_äµP-ª^³ É2 ‘CGB63H»½Fj¿›Ÿô‹óQU6(ZYNÁ­!ù T–0Òâ0ìö« Ÿ‹t²2QÙÄ 2‡g;(` 8™@n=~,kœH†£ªŒGhƒ!Zí7P2}"·9Y^23I» „îË,ëѪxäsJN“^Ô!¤T¢v¸udRûÇ0b EL­‘ÐëÀ®`720'¢³v6,^‰KÆm„­~áíÕÞ¸eÊ}0íùý‰ï¯ýõþÔ_ïOüõîÄßÏ}90áúþJá?ÎüùÞį¯üòRîh¦ïOwƾ½˜üíõâ¯.矌;AÿùfúËåèݬnÃÏ»ßÊë;o˜Ÿ-Æ×êIp«`+9bÌ)3³% "Yy]i›@E©3qÛb¬žÑäS´´F­ÑÈlÖQêõ”¿ R’´ÄZ¯+©gªð•>aWDÖ“Ô‘v" \T‰ Ê{¢*|H…sðZR&ÚDXÒNÖɱñ[l¼v3§ÅHkàÎ8…~%^K­sK±zf£ŒT©¢Ö¨õzv£šÙ &œ“R*ô1õ¼”^Â'Vл?¦u}Ìéýœ;)¦œÚX¸äà>cb?%µÿX/®ÒNðåõ.C‡AZkU5õ™»“~šÏ‚ 9{~jÀŽÓ‰«{,Êf»ªMÅ©Q>Âá“N0°ïü‘Úß;XtÂZ¯ž¦ä¶q‰•BjŠ×®w;õT½¬‡OÔ©„Pø D¯íéü”M…A¹•E®!b+˜S a—LÐA'VRzÏ‘zNƒ¥·ës>нE©¥#«]utüy*î,p@“ˆxx´úÞŽ“@AØÖ»š>ìiÿ”Œ­ ÷œÆµBÇàa“ÎÓzN uÀ]p l#e7ê$]p ¡UW ÚÀÈ"œ°A؆V|/š^|x£á·¢[XÀ!°È˜uòyµÊ*ïv€‘UãV÷Dl4³Fͪ•ÏX…­}ªž€_ð Š~‘Uï–tzeݬÖ<òn0ÚEí@>»[ß?Û9¸:ò—¯nm÷9Äí.i'«ý#ŸªçòTàÖ…¸„xJA¯Ô°jÌ¢F›´%f'oN»·¦Ý+æá~á…¢þBQ7–M§%#QîTF9;h›òN&Φ'ÙtD£³0â‰ÙY#ýª¥!×HL}y6¼9ÙhÌÞ“J^“C‹O%A3`c„ìÌÕéÀÕÕT1¡ˆ+"nV>*ÎED© ßcìì û„¡>‘^ƒãóxü:½ò <“Y-´ê5D…¸[À‚Ÿ¦NȨ։àm©’ϲzà}ûŒÝsšO¨”ÒUìvf÷Yzçé„Kze!7‘±çÈ™ Ÿžl·›ø ~}ïÎ…èÆL0æáBGš‰é¤³RQ»DÜÅb7RõD@ef½Píì9Éâ5ÐÈçz;>IŽoͬx^ìLÝ[H=¸¾2x´’s}úhkäÙFq-÷h1Vö俼:ôÍ­ÉonyA”·×Ç^_)=ß6ÈÂätôÿ@½Þ9¾4øæò›…çÛG[0˜ÂÄ?‡œ&û‡@‚ç ÙóÍü škÈâÅ+€­Bù\RŠCå7;ïÊÊ"G¨ ¨òA ÓsȦ—²'¹ ñ • =šb‡.¼½9úâÊàþzêøÏê@]ûeOÂþZyG¶×ÆË´“…ú ÅAVPÈö–…‡ÖÙCòú@øá"$ òy|!†ðÞJò ìíÔùÛñÖ/̾½6Ü™CÈ‹KH3–Ÿ÷ÏÏ.þ×›¯oŽŠ~yyŠƒ{v{òÙ ¬@ÿóÓ‹¿² öCäèYB”¡’h¢k‚Ëò™>Ät€œ Cj…¶äóáwO úŒ  ´ŽÞµ¬Ù% ôòÂP--}jˆ:DöŠ—n«€ÚËYA[ €f쯤ž,CS %ÂS#uF–#ã{È®fä?Dì"ÿÑ5¹¸›2eŸ‚4¨Kgw)†nBL þ¨,\>¼€ÐÎ>dx1 ÊîròñJòñzÂGK ˆe=ód €'ñ`!zPöI»‡.ª>ÝÊ_É—?žÜ[íß[‰#t Þ–Ì­Éàæ€íê´ouØ6™TøµX—¼=j&ƬäшdÿRasØ>–\.Ù¿˜‹<žë<YKh³FÒEX‘£Öň$b£ôéqFaSÆ/,„¥QÛk$ǽ¼|X–èBïïc§‚ܨ‡†»\ì>3%ì`ÁŸpÒ'É…e#-H¹KábâlX ýŒMƒ بÃiM!&‡yVØËr™A,Ť²Ð/ƒ4aÓk"[øˆSè·°BvvÄÅɆeSÖ‰‚1Ƽ<­°Ío¥‡³²ñ!1Œ>.Åk¦÷Y™1ÍâhßDÁ †zž>=5„U~#ݯ§æC²d¯Ð/ˆË²a!t}#YÍX^7”R‚– ì³ÃNZ&,œ)ZGÚ¹¼-íØ$Ýa #åÔˆ•é3RÌÒ.;Ì嵃¸Ã¥'š»IʨçÏ{ôd›²5¯U@®#—pNH®•Ç«§„í«¾àzDíÓ!áV^s)­Ø›r|1b¸™‘oF„×3ª×«‰§sÁo.&¿ZýùöØßŸ,|Q4ÞüÛ­‘ßßþz=ñÕZü·GAË_lgwcÂû†gÓ^0=(×¼ìí°èfNûÅãÖ€e.º‘Ò¥Tøé l:¤²°š”ø*» KÏlu‰qž3rRµ…Û® VëèBÌi¹VOoÔ’ë4¤Z «ÕÎíÚ±³Û ”»5$íñðÛÒjXÞ£ì=ã¶{E3#¢!Øy-V^sŸT÷†µ„ o¶º$˜€š¨ VêY>%^ÀÐý9 {JD8'!UjØÀ<ü鼘|^/l“Ð*¹åÿ’ c ÌxÃ#œ Ðêá’ϰzNQ»>P ª¹¤OüV\Ì DshÛFÒ²¤Ÿ´áûÌ=ý „J^¥ZPeS·¡ž ½BÆ<¨„£`Ÿ×‹êA Ûˆ ›¤j~‡FЩcèØ ©JÂj¢ôTê°§…Z-â6+%]ÝXÌ›–4ŒšŽ¨gÕ[ø-S œrVì4òꜲ6¿Ôc×Gí7–BãŽ{ã·W#¥(o&«X1ŽDù·Öúgò–bT3Y°ÃÄjjÈ1’3å"ò'·§6§BËEÇ¥©`1 ¹»šœX›êJª=ŸYU=Óvè. ãJùE‰>¾Ë€Œ)Öf|¥„"îã¸õ¸°“j’·²É§oÂ*1ZmÈÍèIívšTÚ!·IÅjÖ¤%née]0Kú q?âàøLt»Šh–@ôB_ëÖ2—Ç"Å~c¿Sì5Ða^cWb¢NFÐFñzÍÊ.èô²1…TЬ¶´ÒÕê^³Æh 0êAxâN£Vg¦±YÀÏA·Qød7çSÇ;Ó—FüCÉ…”ùøÒØ7w7ïÍF/%ŸÀØ´–. ²=®õòr ÑÝÈ// "_º Sûýµ =O7 /¯¿ÚAåõµ‘—K{„lZ.ßúŽiÔù@Ñh#¼«Û+Ä)7…¢- uÛEÖbÒÈ/µ™G¿•7¢Ì ±h £ ûŸ[áùÖÍþY¶K£¾5¨ÕW×GÑŸ¦¼7&ñ´ÌlpYÞœ€ü¿Ü9\Ë?]/@ød%ïÈþ*°Pªœò^E9à~pòbzðY5ËÀÏ¿ÝâÒ‰e·|º ”]dI ñð@,¼EÏ‘eÓâóKÅgÛðÖnä÷7ó»k©GÀ?+©Ç«©'kx'y¯Àcæ×?¸˜Û¿˜ÞÛHíoež^Ê^Êí®'Aö7³{kéQÏTH–±Ð'“ÊÉŒú°5âgƒ¼éC)*ŽI·g|CaQÂLžM¨¶m—‡;î©€x,*‰I3}ìÁ°°¥<ì°¸NæŒÃ uÔÅ Ù…¨b$­öHø¹ŒæGŤb8¥‹"ŸJf'| YLhF³F@štH”ðq¡K ØIVÄŸÃ ;Â<W0,L¸G FP–&½Ó%k)¥J©SN¿[4Yp¤ÒxŸ(”¥€¤!€§˜TYUØò/xiw6¤ré¨~;˜,QdÍó#^ tPæ·2Gú²AyÄÎ-„UÐaLŒ‰1š2ä#âÑœzªhÉÂŒOZLʆÓÊRJ’ pá$æCìtÉ45`ZtL¦Œ#ýšˆ™áTâ¼bŸ–À£ƒ4µJÎjÐð[Ë{!Ϋ­BZ•š×f”tCèÒ’LR,[! WíP:O)Õy]…Wq[jBÊ'ñ™¨1ÃȨ÷ñ®XîZ_,‡ö§FL׳ê‡#¶¯7³ûÓ¾·«ñç³¾¿Þ›üûãù'cŽãùÀ×ëÉ_^.Û¼\ ÿîúЯ®^]ˆü|+óíF äétß›•~4Áþ˜ëj¿l+,ÚŒˆwRÚ;%ûÁ|ÿÍ×€‰>Þ'v‰LÌF~ûç&V«•×íõ˜¸mF£C„U’jÕ”z)¾ÒÎïòHq.ÆÌl51ZôÔŸ¤×ÍǨz«â:Z¿Šè`7¼¬‰áàµìœ¸Žœµ±"jBŸ´Û.hë“cCˆ_ÝkµÙ…QCËhðkˆ1 “×SÁÇV𰬮“|ünO…„V-!Wrz+TåÁöS錒]§`Õª¹ 0ËVóL’6ƒ¨Œ2FµˆrŽG¬@>£'i wýÈ$kÔkÚŽ¤!e±([„¬ªV‡¦=h'˜äM ÜbF…VTKéü1±í=1­Bï±È[‚V‚ÏÔcU´jµ™Ç,k6Jx¤j&î¬˜ÙØÛö)Z+ã¶’±½ŸÒçìFà.½N.îTJº”*<¦‚€­ Îs™trµÛÊ¡7 Ð–Óʶhb~»˜×&@¶4W‰¨u ,Rµ„ÛŠJŒQ‹1BfDI¹m%A'ò‰•J>â½aàÏȹ-€4Ä®,Â9­¸ .é½§¹äJ`£^ÂndÎÏØ0{NÕ™—ɧ^] °Í,ÃHèµ@L«öèIZ~‹ŠÓ`µ«X z­œ^ÃÁ|fXb68MQ}À'öÈ{BZÒlʘ²q².®]ÜžóðâVzó‰áN©¨•‹yËÎlÂ~õÕÝÙ?½¹±w¹4SIˆF^ƒ‘Woâ7xUÝ6q ôWÛ³Þ¥!ëÍÕþ[é±”|2£šÌªÒú½ÍÌ•ÅþÙAûâˆÇ é+˜ qU* Þ¿5±1éG> ä¼P0_ž Ü\?Þ)~qµ8W²MÚ¦ V­°Õ(î@þ¿ž¯äÖ¦|Ü”Ÿ×g$ ¥¹`¢ ˆ :,Ÿ]c³ÐlVŠ×Ç„vèd»b1,‚AÓã0S<ºQ?G‹M×cSa¼Fb¿››ò‰ã!PAŒ“3Ûü&®^ˆU²Û£qÄ. ˜¹F Ö$Åø¬‹ªÝeÀ…=L•ì0²TÔ"â7ëÔ8.·×[ÌÃá·’iµz_ÜEe6Ðéõ~Ècby ôõ±ÐÑÍÙ½­Ñ‘ Ò-ìöIqkyǓң•ìÑ¥2ž–cïȳŒ|ýïrZ¼x°’(ˆ¸ô¢|"ýàÌ£E˜#§ÌG,ônä^_}scäíÍqTùòúØ««ÃÈØ´5ðƒ #T”WJȱú<èH À%áI°Y8BË! ,³`„ ¦ð_,Æ/' >²rvÐ÷¥¶ÐA DÈ ª:ä Ê7·§@ye°@m!@pJ#0РPw¼ý¡P& "` ÄX&AÄ. J‚Âû³¡½¥8²g»Œki`€=ÄO4'òI룀7BÑàW_ÌÿééÅ2íÌþf÷ÂÖÞ" £¿~´—~wùæäíÉ?®Cšwu9Fxq¯ ¨ ðhÐ\ïZm´mAß½²’åñRâp&ay)ªÊ;ýÙÀ-²go9Êó­A¸bá.¸Do„(ƒ5hŸ<$8Xˬæö–xR{KégkÙãýåȰôjêÙ*r¢ÿÙZºüÁ8²Áx Ùt¸‘ýñrä8ùzuø@GKЪY(ª/âÆYŒÝŸÛÀËðáDÐ'B¶Ÿ­g‘]=@>9 ¢›sÁ›³Ëý×Ö“O¶²‡—,Å/ZǃâAg0$N*V'ÝÅ8ЂâÊrä¸}<¯ˆŠûíÔ¸“>›ÕNÅ•éjÞ4SÌç ãIeÖÇ…nVŸ¾7Lç YÃxð@S¤Á˜ªWå"Š˜jvÈ:;lɆEù~ÉpZ0×D¸¸ãyËDÁ‚ÐŽŸŸðs}V‚UÕ°R]º^ K€…Æ\ユñ¢yiÆ7˜RMš§Š–b\9Ð/™È›X–'Âù¨*’÷{xˆï( pÙ°x¼`ÌGP Ýg¦gm…¨vbÐ1œ5æbª‰¢mjд1¡2fzÀVŠk‚ºS‡nÜ­%Dm¬ñ¬q4£ΨJ)E1!ƒÕãÒ¸Âñ‚n,§J+'ô“ƒ†‰¼aiØ3Íf­YHÍnôh Ï­ÂKéµ0@À¨ƒ…ˆZ£‰‚Û(eÕËYA+Ëk éE*n«˜VË…±‘sRFÄÄ]vÁªÀ™9Í |Å\TúÅœïfÉø`ļ;nýr%ôr)òÍfú··Æ—ú¿»”1ü3»?ñêBøx>¸?áþîRî/¦^.†^/EŸÏ®Æåßn¤Á~8åK@ßßy6Ów8áÙuÞÌj·zû•_Œ{î{¶²†!{Ò/Ih©zZ¬ç¼]Ðí•*jØ@7ó:<2¼žÑlâ´ø•D´7¤"Û¸^1ÎHovñº£*ª•ÙÀ–ãݼ¶ñ€l.®ËZ˜³ýš¢GЯ#9øÀ6m} lÔ@öÈ1.i—æÈ¬z1þ¬IЮ¦×›…VqwZD‰ìáAvõ {{Àidœf1 Ù¥ÃÁŸ†P'jѪeÌzxi¡…)Ÿ" Îk(9«^Éi2I»]¼‚]ÀBmûˆ×[µ0âޙ߯lÿÄÀi¶Š:‚Z’MØ1›6…tdøeÝrÌÿÇÔ{87–]ŸÿÌVmíVù³eKÍLÏt$ÙÝL`HDÎ9?äœsÎ9’ 0g²ÙÍfçœÃL4I¶%+|¶÷û¼ÖžGŽ][uêÖ¹ç†Ü÷ðÎïF¬UìóY}HGZÊ›””Ë’ÉóI+#n¢eþÜbÎød·üêF²q±¿Ðózµì 鼞ß'§]Êûø e“_?é@° UË|Åœ òÂ6Š[?¾ÙöU"²Z9XŽû­Ô©vÓêÖidµnŸN(¶êöW7+­„l­im5í²îd;»2ån¦uÕxÆ'¨&iÏ¢Ž9!+Ù(ÈøÙ Ç›‰õ‡€ú•Í$”63Y­µYH2I"ÇdÒí4Oê”C{¦j*&¥> Ñ®ÅyÑi'ElŒ„‡ŸõK’qÐ̳#”¨M²òöY6éÓ3„ð¡8|MøâOPðC‰ñýiƒCsA“‚Qhü3@GBN±Úçù*šáÎ zû. S?7è9žònLË ÕrR½™ÕoæÀ0PðÞ\*–?¨96Ð ¶ PÛ£ÅÔðucÜkGž-çž®ä |²œz¼”€ðÕZîúwð¹Wk©W+IÐ8À?èFªÈtZ*òxž³y¨0诶rOçÙéHÝ#t=3ðL ZøífþõjöÅr ȾÜhx*î´ƒNiçÙ)ƒ¡­±œ|³–û°]æA§º£OÖÓ'‹áÕ’©“Óo6­ 9íZÅrØößZ mÖlÓQYÍÇoÅi73,Õ-q7#â¤-MY 1±ÇDpéÇ=Ɖ\ˆ¿X3—‚‚¸ÚΪòºrH:°P+ƒÌä´+5Ô,T¬sES.$:9€=À<¹°¼œ@œz|%%/Æ¥¹ˆ8á¸ô$·žlVŽgƒÒjJ›ò‹<&RÈÁHø¸q3áe¹u“V%.áâ42`¡J q[È&®’U[µã€FÕ´*éåyôD\@RMè*ð"5Ççºõ¤b\Þ*Ë %( õŒ.sóji}.„Ø5¤tHZËé3aY*<&ÚXQ¯’ÔTê¹’µ’{ôä°•…ÖE抸ÀXØÉˆ¸17+ äÀwoÔÈ©«i,ðá³!a!&™ÎëË!yÚÉGOuæ©Ø}z>F0yALº²2c®[G‚>²I†³"xûºV< lcWx”œ~há ²ðçù$tÑŽA‚Rº@ ˜™ ƒQLírËÇõô®µŒúÅj丢?Ê) È»÷Ç•Èw‰ßî—^Ìù~·—?ïÿß÷fþå°x“|»y¿ø´ýóqý»Õè7ò­ ÿ¯O½™zÛö½›÷ÿv'ûÚ ?ª˜¿Y ?ŸrÞ.èöÈãiÏzXQ66îRLGHF¯žÖgåZxX›p܇äw!_5°1 VÞˆ|²Ë'%h)×#*ª™…ÑSz“:¦G4ýM7ÄÎ,Ùy3!e!ämœ‚ë–buì¢áˆŽ5PÔŒn=§ÜŸU4$&^à1 †´ ù *”.­Ã›1Òuœþ²Wº˜3«éÝ‹9SÙ'úÓÇÃfTù|¿úë§«ãWþÏ¢Wxo=»7` ý4ie¾½5ýŸ¿{ðú攂ޥæ\÷j&Uœk ;ùU+g‚&t¦ºÛ@öš¨Ð‡š)Y|6†]ƒ_™r­': û|Ý÷²ìú±¥¦µÓ0¯6,S éÝÕèƒÍDÙÏI¹©Í´´T"⬗W‰Êcv:ôÝàmv7¿Ú´g¼œŒ—•õ±óAN#)½·›zt˜óšð/Ó¨ÁYõãÝ“ø…Ç2YË©ì:¬VÚë·Š1a--«$ĵŒ¤‘“ÕÓ²BX rgršfV³P¶­e—Rºwµ˜ÉÙS.aÒ-šJé“¡Ž?¨`_͆3m&ÄO†xA'ݪ‹úx+Íkg&Â2¥dD%Çix­›Çéý’Ã$“{8Œˆ7Ägö3'¯h¸NÅ»ÞððX…#né˜Õ‡P®4Ê—{¥çé ¡7ÛàqÂwf½ïv ŸŽj¿:n~w³ Èóõäͦ’ž­‚;‹5=7§¼ à.ëNð§ßŽ¥üêv åÃNü¨é†Ô38¹ÛŸÌ…oµ{UçÃNòåVñþbüÎ\x·b°”¸;}µ  wPwƒþþ þb³òl=ò|#ÿh9u<„âzÔôÞhzoNûAº–…‚ov+¯wÊOW2/7 ÏV³Ï×sow+ /6òß6ßîTÀò|-÷z«ô~¯önÅ!`@‹³s„@¾=¬žNrå(Є¶ógö³£„^md.FÐ9šÓåÙ!g`ƒnB·ÞÇÎR¡ìÙ8pÂݹÀ[tÈ(©µޜ­ñF·­e>ÕÿåIçOÏWsg®ûa¯üÏ¡,º÷m99p~¸3ó~·ѳcŽžtâPíývj¾ÙtÝh¸Ž¦|÷bhKoWl— 17r¦¼Zd§l¿=‚V‚<ЀíÕfÿfÓ·[qܨ{ ÃnÉúvÁºš1¬gŒPv-m˜ +nMùáî̇•,íˆr#k‚+BcÂí€{÷°tZ„‹>î¤àæ>_‡[V‚ðq'-ÿj£ð|-óv³ðn«øl1ñj5Àó¨~»š~½vºÊ}#ór3 Ê)áÄî/„y~îÌN×ä  T†6K;út.òa£ð úOEt i#÷éfh0hçÍFîÝFáíZîát2ŸÛ›ÝÒQË;ŸÖNÅI;#ëb5â Ÿ–P Kò†¹”j&¡L[i1)n¡ƒ‚fR‘ñssaa*ÀuêÇá·¦•ÀO;ŸTÓŠ°ƒæÐŽ#¢jB^  y=.K¹˜„²™DFbÆËí4œ [ÄÁIù„µ”&–NøÌ4»z,nÌùÒ~>¼šªIM)ŠÔSºéœ ¤ÓôÌ•Ía;#h¥–â@ ÂBPœrs‹axi(3!Q5­nôð“w™Év=!ìdbòbT^‰)JaiÔʪ$4­¼ÕgbÄœ¼zZ{6c×Ê…E >Å¢ Z˜óG!Œ˜x‹†‹)ycÜ'Lx…a;^•Àc™åcñˆQŠO>õŠ1¯‰¦ i…#<":°CÃ}é5ÒíªIöÄ  ÒÐgf9^#Ó®Bh”Œæ fFO;,y±=®Òòg3¶Ã„ø0¥x1çùÍ~éÃJüOÇ?Ü(ÿñF€çù”ãÕ¬ç‡í `Ïov²8ª|¿‘¼W2€åßîµ~½•þv%ú¨fùÝnîßïÏ@ê~Dú‡ÃÊ‹–{+,Ú)2Ú%¯hÆÍ[‰iVSú)¯\O¿Îîÿ,¬¡Ù„cFΰ]<¦¢õá8Äã:æ€pì‚™; 8ä“MÚ¸À6£&æ Öç“Br’`u Gâr3 ›+â:Йӧc^K˜h6ÑY01P¼Þ. h&¼~ÁÄ9ýªšÓ§á@(œ¼È!œ?Û@ 4¦ÖÅžàÕ%\4›±‘4¼.î'âç»At]HþÂ$ð™&<ú1Pœ¬K‹ ÛÉ·KDûÊkÄGT6©›>yYÀìÔ¸ú·8ÌgØ¡Ïlx`ÜAÒäE>»ŸM¿>9ö5Q;{,4R—˜?üÃaô‚€Ý ¡¨äRÁHÏßtÿÊø1{Eê‚Ê!æ :eT@QFeüa6¥K#Á™U“:€G@GwŠðð©ÝRvÀØÕâQ9o˜‡C¾Fx8%ô*ösî ÐpÎæa…Ô.µ ØáöƒX•x€t%½G/NºóEk%‚ ¬^ér@G ÔŸ Æ¿^*ÚfR†–j {jF˜ð5ΓÝrÖÉ}qP_)Û„øssi]ÎÅŒÙJ¨÷ZwÛ·WR·a]ÙØ&ñˆU9Λ¼`E&æ*örB]Lhc‘QE°jI•Œ¡”Ö'’BD2SÔmÎ{×nlÇWæ]wö3û@+£(¸ 9d± j¥eËuÃLAu’‚&bÀ@l&B@XÈÊÆoÏßÙJ:‘ázRzw+ùîþÔN`®¨šÉ"a;Ém8A;Õm˜€bgÊ:SÔxãÅg¡¢ÚY°ÝZ÷ßÛ‹­zwÛmx1ê7Wb;³þí¹ÀJÍ 5“1ç4Ðç –´›—°³ç‹ÆfN•ðZ¼‡ú Ÿš‹ÉœF¢Z2dÕŒ«lZ—ˆ; `÷)Å8>ð-”Eëp‡eœaNvÉXƒÕ˜~.kmDU5É­ƒŸB½\ö _¢ÿF]éo+ЭFÏ_ Ô¬g‹(ÀrgÖ·[6ߨÛïÎ ÛÝvú×c¾ß¯¾ØÈ>[Ë|{ÔøæFô³Q…³‰§»óaðGwæ‚À9€%OV3à”Áÿ‚§†(¸iÀ P FH ÆÇ+i(!DÁÂK‚w _?!d€‚/¶Š ï÷̦AÞíÕÀ)CäÍvôWàO3|{cêÃ~°>ä°ì•N÷‘¡òfë¿öÔoÞ¢8”?“³è™åL9J>YŽ£»òWSŸnÔß¡‡1fнlèÊí•{í ºei3ûh1úb=s–òߟ=^Šý›ƒêYYtfj%ùçkÀWOÑ5y0¢ù÷Ë?®ý>½‚‚·¦Ü7êŽý²u»hêÛ¯¹›­¢t €“Í‚ ,+”RcöOÉPóFËmxÜCÎÍ’u=g‚=ú©bG –¬ód! ­z¯“@+)Û–SºÝº ­-g„§QRZÍ›:IíJÖ—†ÚàfíÔœ'ó‰G+ù§Å'ë…§kù'+Ù;³áýŠó¤|0¾7y¿Ux²}µœ|Þ‰£ ¸š|¾ž~qJ>/6ѳE÷ýmeÁþp) òx%~6§ùb-ýº“~±·š{¿Uày±Ž>fO×2¯·‹OÆ|±’~¹’y·ž¹€lû“ÕôfÅÚŠ!­”ªW¢²j¨Èxyq3é`µRH=")ûõ°d6£šÍiê y#ƒ”²¸—‹‰Ã^f½¨N†ù±»’Ò|\󰀎æòÚ¥Š)ãag}üvÑ0•V5Sªõ–¯S¸tĈƒðòaYgÊÛÌR>Þ\Ű6ãØ[E3°G«`I¸6!æâNåô¥˜"d£¥üœ¥ºµ7.Í­¬¾•gƒâLD’ ‰x,Z¼Í0™„œÑÌŒš}¥île …Hhº{€^#®8•Óf‚RL5äÃÒ©œaw1\°0ÃvÐÉØY6Ýd!ª(Æ ²ÑR àI{…n-1ãçÒRHÞà‰(¢NNÐÊ‚Tè…ÅÝüRLUŒ)« M!"¹øè— ÉC6–AŠÕ ‡Ôœ~—’u‹ {k•¡ !£v»ÕDðÐM†Ž³œÝ î=lG=©í(ØýBj·AŠÝgd˜dã2f¯ˆÚI*þà”q]Î7ÉØy<‹1ù­†õnÓú¶x>k¿STÝ-Þ.~«ÌóOûEÀ›?”~·yÙrÿz+óçv>­Ä¾ßLêüj# ¨ó¿µ£üû…·óþ?Þªÿå¤ù~1øq1ü?oOýr=¹ìaßÊÖò­¸zÊÁ.h-¿´åWX¹Îàç. ^N¼*™è²°Jê5%ùªK:á– ì!«ë{$ÄÌ2ÐúíÜQ`Ÿï—á}Rœ_>ž0PsVvTCrI±^ŸM:³N5Ù«§¤½Ò¬_w ÂV¼á]Z’WG³!:ÁÈðX$¸®bõ„Ã)˜ƒŽí0rV/Ú;æ h…Cz)V¼ ooîä%>¹Ëk ó <Œ€ÒpÁ¢æƒK…ë2œrÈ 5ŒÙBÌȰñvJ¦‚öNÃòi+q»¤>Î)7ío¿ÚÉ·•ý´–x3çûs¼Q~Ò°}ìDþx«0óa) ÀóÝZâÓZü÷7*ÿëáÜ?–@ 2ð|³ù¸ùýaå7;ù›9Í£†k;Žì¦t3-­"ÖœüªS䎪HW“&Ž=¬¢öðÈÉ=²Én ½ÏÄVÓz¥—u̧ç;ø8oÔÂ21|\!{¥ã%ú×çNÑH@Eð)ñNÉ((JÊp‚)Ö"rÊp@;fñ Á;&ሌÒ-!]AX½<\ÂéT á<ø_ë:‚Û§`èã_ö°ðç€muØ_jø(çäü0p¿ b~‚íù¿hØŸƒEÍïƒGÅg$ZdŸžàTá²EØÝí¸M1 Š]9â7 I ¸5cVùŒ~QLûÚ¡9i¯[B?oQbÔü@ ÐÒ~À:á wOïcQ®íHx#1ö™p‘Iì|E;Ϧôü9èÖˆVNPKÆÅìA•x ãx´«ƒ«tgŒ}*<·l³OÅÅ˜ÄØ‰ÙL²^i%¤òi©jFŸ]‚w+& < žÓ›²±ªA©n&©5 0õü»Kåx-$‹(<ìçÀïnÏl6½EŸ„Ø÷“fBÛHhk1õoöR^1¦ëÿ6)ð ¿<B">y.að:Ŭ5RfC²˜ =?'àb„<Œ|RZÎ(§‹Ú“ÍÄñZìÓ“ö?ØüÓw{?¼^¾¹=ÞŠáÌç´ õˆx¥j:^ ÏçÕóÞNY¸à],jZ)q+-ɸ(aÓXЄ›+kom%æJºˆ/xZö»øÚjU}жtŠÒŒk,lÂDLCµ(óÆ “.祴sŠƒyÏæ´}¹lì”Ì[ÓžÙŒv>oX.[‹¦ù¼nµfÝšv¬6¬õ¤¬–”52JxÆÔÂëQ¯];î5QLÈxÈÉÉD ÓibÚM » +ä KøXk@ÀÁˆáyŒÄ]´‹?Ÿ5ܰ P⌬n·St±^îj^^ÑÊ|°ÿËëíͲÙ+œ,„~¸7ÿÝIë7€ˆž­g,ÇßîU@ùæ¨ñj«p2z»W¾9íuòúÚa9D_læ@,ÅÁx¼aÉÚððgBÒ›Å-ÿÁ”÷d!¶×ð¤ŒÔå¬ñõ^õ^'`Fcæ#wãO7ó¯÷k·æB 'óQ°@„ÿ­€ñ¸>áÇëÙ;%Èÿ|=Tóá ñ~Ã9½ªsc (è {@Ô×§,ôíÑô³Ut(ˆâÍv=zhå™{eˆžm.{}:>ƒÎpmÞï•Þ¢„ XÀ~–ôútìåábôq'ÐBÛ…Óc ÏVSgùŸ­$A‡üó)ºH ]Yô-`:_öãu!ç'3íùZúÓQýÃéZîÓE;åïn6¡ò›M÷IËw÷t9ÍQÝyÜò¯çLó @´ê^ݽY²œEZ·«Ž•œ ,ÐæÐ\{Mï:@NθQµß˜ í4=«%+„›5ÇÞ´÷`&°;…fXÌæ’šVY+ÛŽæÃy6¡®%‚¾ÑoÕ]Û w|qXÞŠ)WŠ–å¢y:ŠÌ¥ ó9óRÞº˜‹u³âÜ,ÚW3¦Í‚e-c\IkïµÃÏ–S÷[¡çèܺqçÍ.º¡]ó}´ŒŽç5]<ÏÖÓwÚAPž®&o·¼ÏÏx–’Ï—Á’¾»;hy·jöÛ á»+ÿ`1ö y³ž½œy»–{0~¼œ¼=j…i;;åâûM4½tT#ŸŒÏÆ4)±:ñ ßDŽ;Õx&®¬‡Äi+½U£’L€WŒ‰#.ôEâ¶MZ¬ßI {™ ©¿žPL¥”‹eÓ|Q´S ¡ÃGµ¸¢žÒ”¢Š„W´¶³“^a3gJúD 5+°GÜÅÊøù´®•7Uâêl@±s‚ffÚ+Š9ص„ª–TÚÑ”›U É æÙ¬j+DdaÇk¥F}¼€“™Ë§ †FF[#­Œn1ošËgsfà™j\wñÂ6f1*mdfFÕ*hç+¦fF³Üp-Õ]ó{#­Ïø%…¨2“ÍÖm©¸žÕ6óúb\usšYÝlÉÜÊ !)ZÎ0•Öæý’VÞ\)Cf¶I:Žpb@ŽUAðêéÀ9. ,ÀBv„a5Œä¼â”ƒ¯çalòq—jÒ*óë©§ÿáŽ×‹GmÁ®ž4+ÆíZ¢QŽ3ÉÆ•ÜAÀ·Žª‚.¦_3+&T<ŒV8¢ C@ Σ§éÄèÁÛD£V^ÿnÙ¼WÒß,럷ݷ‹ê;EÍQFõz!ðíFú‡½â¿Þmýá¨ò×çË?le~³›ûËÉÔ‡¥Ð¯·2ÿtP|Þrt¿Û+üévÂ?Þª¿[þã~ñÏ'Íï7ÓBÿ|P(z5ï?Liwªõˆ²neÖmÜ’•VLªÈݱ'c˜¸£ZÆ !›ø£zFI¹¦cöˆñ—ÜR‚‡u‹ñÎ0`WL031`¡‘«#ãô[‘I›ŠXŒ¨ÂvžßÈÌà-aL¹DNIÁèÕóGÌ"œŒr5 ¥Ôd#oÀ.­¤€7{3AðË<Ü x‘‰{9½[Åèèa;|²’þîaGL85Ñ[I½UŒ½¹œ®FU䡟ËÙýùˆ*î“Ùô´ˆWp‹šewÔ'5)Æ#Nn:,ó9X‘€ —Bb~A*,®%èœl¤Ž×w¶2Ÿ,œìg7ç½Oo”þð~ë÷o7îm$K>îrI_ ò×리“öx'ý›‹¿·òÝãÖ~Û¾^×u|«-{gÊ:_5·‹Æ…’p%çdΧ䷗¼7‚÷Ö<Ç+Ž{[~;á·wKKUu>À˜ÎH×§-;mÏNÛ»\µ­Ô ;Ó‰Œùt“•°l©bÞšñlϺ;USÊÃh¤d•„4`!Z£cÓŒ'||D„Ñ+q‘Ϻ*àöª•ã"F%›8¢FA–±J^£œ±{á~e½ÂzH–2RJvÆjF³QÔ/§‘ºŸ_rrm‚~·x$ª›rI†#ŸÅôÄVXî••xŸ[q V Æ·‡õ—»¥Ã¦çx&p{6øn¿zo!je][ˆ!àbvÏ7²à‘·K–ƒºóVËWurëÁÇS‡ÓÁšç¸»×IO7gÂßÏþl³ô|«|9sk6²ßðAøt£t<½=l“¹³¸·”‚ðÿo¹»˜„"×òPöÕnýínýãáôw·æ@¾9l½ßo~{4óýÉèôv§þá` äÍN ¢ßܘ>=D±ò~¯ òv§„.l^ϼ8]}Š@è®®7èVîtØ9ÓÑãOIæl-ôKtyOôþ|”oN÷¦GtJM¥k>)‚R(ó¬$Áˆ.«X:µƒt<ã{¼’€ èq‚íÈ‹•ÌÛ­Ò›Ò£µôΔ{&©Êy¸õ¨¬Ճ⬅^¶³7²ÆãºÈêI'ÏáÑL°W¼¢|H–öIœŠÏÆñX©°,ù­T§n£Ÿ(x­¸²äáÅdžZLV kIEÂÇ)Ä$n 1ìaæâ’°›á±ÀXNÈ‚&ün> ZªZÊ–´‡ |²Twf³bÌ®&„œ|D‰ÒEP” ÊqyÀBIyEËMo)ªò› '="mÈxÜÉmf5½ž¯”í ’u:£ËDA' P'èb'Ãâ\B‘ÊÀX ËêqÜz™N:uÏTÖUð$Üœéœf¹i‡p¦¨/Ee‹5 ßJ/'źs½åƒoÑÌè€ßŠ!iÈÄHûÄq§ÞáZÁˆ”~]/Âù€âÑÑ|zØ!t¨H€@ù„Q2æTL€xá­.ñé©q;7éäý²·YQº R¬[O±(ð6À%^Ága–OX”!¥[ÆêÔñèéN BHò™˜Å„^ŒÕ‰Ð>uÂÌŠjI›ÃQݲŸSf‘»Uýí‚j#,z³|ÑåDDOñ!&þ U4 Àcbä”+½Û,ö¨ ÅN8qAAí‘Sº¥än«aôÊ(ÝrÆ5 wðÇ¡påcçDÔnp¾"::ÿ"¢^–³®Jè]9–=q8G#Pq{…ä‹2F7 ó÷ÉpÊ+æ)„%ÙHðtœžç¨•Ê'|!¥^àâ?‘Îù˜%ƒFQ?ÐŽW‹÷&-Ê!)ã¢I6¨äv‰¨_ɘ9„Ïôâ^¿yÒ©Á‚Ÿ¢º(—9´^&ù*‡Þ Àä^#N\¢’º¡Τ]ÂÑj(1ŽF½:I¸Äfõ øC*„H§_þa1{m8œ&£wtäs,öKȃÞøÁ|Æ¢\¤a»o IÆáP®òé½ôÉ+$ÜׄÑsÃ_P'.q¨×Èãpƒ?“°ø´@æä%öKúÄp#g`´÷ïhø¯Ù¤ËxÌÏÎ옟3…T”g¸ÄKZá°ŠáL^dŸÎs ÈWÄôóºN< ŠI6¦““±_ˆ×ÒÝ:ÚþrÆ¡&? ‹ ïV“}::Ž;d‘Œ—ƒÊ°Ž`"#]Ê:y¶C€u ^a;c¸³šÑ±{á‘Ø¨»ùhùÞzîåAÍ%É98³)’zy£á¹±w"“ ÊVŽUCòY9n3»˜2¤#*32÷Ic>‰ÃÌpÚÛmcz-Œé‚:\·7²µ8¢bºÇDrèÆïm§ÿòiÿÿýíÉŸ¿ÝÛhXx¾ôâFakÊV –+ÚÍ)ó“½ôË›ùõ†~*!l¥¥NV'éÙàe¥Z®ºln.Dm¤žl'Ÿí%^%_¥l÷Ú–åªz¾„œl†·çÛsŽÝÏíõøÞb°G*1yÄLC8W…“_DƒÕ¨üÆrôp)<•A6fàTN•ò²³!aÐA³¨p&V!ì—‰å" |‘Aë 1\n?<3bÁ¨7*äÁÓ"‘Œj<‡và¿QÞD÷¶Äßì¾?n~\ûá¤ùíÍêzÑlâöÙ˜„™Þ ÊE&úbÎx0íOÛXi3=¤%N‡³IÍóòû›Ó's¡gëÙ÷µ_Þžyµ]ô1S>ÑfÁÜô ·KÖ¹°¼dc͸Vi)k¢M[!$mdøeø†O–61g#ªŠKX°q7ËÎ;‹É“…„·f£7g#Vó'óqˆžÙï.¦,gî-¥o,-¥ïw~ЬdŸ¬žm–v@r@Þí5_mU€mÀxón¯ ˆBÑ×Û•·;åW›yô?RÑÁœÂóÓí`žžXx¿,„¼ ››6ógÃ>g¨ó_ã?(½›3ÎçLÚ)Ù èe³å_.ÛwçB;­ÀjÝz»`}­éÚÖL`kÊ·Rs®×Ý{íðá|òÏæ-K×rͽP´ÍåÍ+”‹v§ü[U÷nݽU²ŸÌF,Åo·wgÛÜ‹qd³`¼9í}°ªy¿‘y¹•º–†( ÎfÁ´’Öl—Ì@>/6³Pèz›³®Íóy]+£.G$¤²WÃ’¬ƒUqóVÒº²íd6ø¸ƒâÓfÙ^ HÒ.AÊ+Lå.3#èZ dŸ“ ¼fRØA¯Æ¥õ°4m¥ÇÌ”rPX‹óèÒi:ÈŸ­}fR!*^¨[€s2AÔ^‹Ë!ÏæŒ§SµT"’jLÑ.šæKæ|@spÖgüðV‰:¹ù°¼–Ö5s¦vÅ1S0”c²…ªµ’PÖ’ÚÅš+”—c*À°˜“_Œ(‹9F Ýl.^ª¢hZ É+A@/IÈÎ 8˜1¨Ü/˜®X*9]:,ɇe³ÓRÑÚNë[ M%Œ,Tœs%[§áÚ˜õ-7k3î•)ÇbͲX·æCâÙ’i:g(E‘bÏ– É*Íâ´;†l·@NPˆÉ Qy»‚VÀ“JÁ!F¬ 2á7ÑÝ:²ÏÀp¨‰fÞ¥%Þè„Ãfè§Ã{[O÷h6å$ ¤jyýš]†O:øa3baûõ´°™³q ³U9¡äôÛ‚K‡ž0¦EÿÛbœK¼l‚‚ÂQ„‡¯`ØcÈ‘à€vªž k–MÅ4eè¥Õ{eÃaA{«¨}¹àyÜ4ß©˜¾ßɾ[ŽÝ¯;þt»ùŸO—þúrõÛNøù´ëAÕs¯lz·Èy»ÚyÞrÿûƒÙÿõhþwûÅ;EÃ?–ÿíþ,äüÝ^ñûÍÌ÷[Ùg³Þ—s¡Õ t;©k‚»ñÉÖ2¦vDë•M ÄlÒÀÉ:$怖5hæcMÜ=ƒP{Êu‡ïŽ;„cföPLËHêØ!%%kæ{%x¿‚ÓQxªÉ˜ ø¾¨‘†rŽc™%£GÇðéHzÁ B¿nŽxPTŒ^%ý:ˆú¶¬>ÎøW@DÆ5 §[ÎêeM|%¢v Ñ¿?è¶Qr®Óq¿ £XÖCù™Eƒ‡‡®d¾wq…”KàøJœ^Ø ¢æô(™W“_R‡þNLþš‰ý) 9º §›4ð7B&ñ€š{ÕŒ84ؘ‹n–cäìËi¿Œy‰OúÒ­;Âí¶«FôA: P§„Mí°‡!*âcYô>)Ä©‡J¹F§õ £@;R ŽÃæ!“º@hÔ<þ<›Ýv ¹›D¼ âä%iän*á2€ û•5`P’¤¼aÚÄ%@ ö)±É=€:À? LR7›Ò#b¢ã9Rv¿qŒ;GýœŽGWï°ˆ—„ PG@¿: Ð…Õ+e^ç“. (]f¨¢ À<€7 vˆM5)g÷Z”x%wŒØJýùÓÞóÃÂvËþòfád-´XTuÊ굺®•—Ì€i,`%E\¬¤WhSâ:ÚFÝ»˜3­”Ì[us'¯Üž6Ü\²oϘöÚ6@Û[ƒÿz˶Ô0ßX‰¬¶\¹€°•7ùô$ždWŽk}NÕøTNïÕvIwo7RÏ(’^fÒÏŽ¸™É0àÙÍd»…æ°1L&ŠÑDUªðl^¯GM–ˆÇ9\ ‰t…ɺ.—Ž*¥#~3­–>Z?]&ºšø¸{½û´—ûÓù»¸œ|YÇê…nûÃÂö´?¢£dÜNÁšw J^1è«%{Òʪ‡Û5×í¹Ð=tkâ îø‰"ø…²ž3,ŒÓµÄÙ;íð“Õô§[ÓB'÷ÃÖ»Ãé—;µNÆ´”2¦ŒŒù„®á—ÍFÔWs¯öêÖò϶ÊÞš‹ž ãÜ]JÝEv’÷;™ÇëÈv¦?XÎÞë¤Ïp¢Ï6KP0æÕvçõvíÙzä,úá`Mݪ€þéæìÇÃÖ‹ `’:…„.§A±äÇÃöÊ/Ö³`y¿_Aè°z.ÐÙA7owJt–ÿ=J,è°Ï‡ƒê§›Íµ—9¨í,çÙ9BèûmtœçùéÂ(ò ]{}:‚tvNš´•GOÎ9­ÿ¬ÚgkéWèˆPñq'ù.ÅîµÃ€=ÞlºëΛӾ[­ ðÌVèlÜ ”Ûí8„S°ìÕ}B ƒ?îƒéÀfÓ»>í]®9[9C+£ïÔÛ3°ìµ#kSà¸Ëu×JÝÕ©9 tð×mZþõ)´Øã!ä„ÔÕ¦{­á†œ‹%[»d^šJk›ãbÍ½Ö tªîÙœ¹]°®VÝ› ÏrÎ`½h=¨{Žšþ½Šk|tH>’.%­¢éhÊ}§z؉=^I>]KÝž ä,Æ•3AIÝÍYÏ-'î´#%kÖÆð«ñ!)í畲T€_ŒKS^%*­D¤Í¨Þ€¸QTíuÇLDYö‰2naÔÅ‹¸EfÙf¦k‘q«‰â´RFüŽ žé,ÒˆÈÒ6FÁëGЪ¢r!!°ÍlɱÓnvÆË-…‹eÓzݾ9åZª˜Wê6Ýv`©j«'”À<39½ßD-š—§}‹uu÷lÉÖ*ÙækÎl@èÓâV!(n&usyËLμ=ð騵˜z¥á†Æ„zêIå|ÉØŠk nüèr^aÊ/Œñ€0ìçGÓÜl¯ž J:ûZ͵\´A;·Ò¦ŒGœt JaY+§[i8Wö…Šq¥iëÔmI7§‘TÁmªDzBwñCv6Ô™O(¢Šqe* jæ Õ´f:olWmð:uWÆ/Jºù<.õ¼T-Rœ[C%hb@×Ò,ÅÙx«ltô·ì܉µ±£6Ž[Mr(&RNÁLÆTjr>)|Gø¦.Ñ$Å,Aüô—%ÌkJþ KK ˜ÙjÁ°V„õµ¤Á©¥(=Ácük#ÐdRó113gcoä k)åqÍôn%ü~5ôi=zR2¾hû>®§Þt¢ÿvî?Ÿuþú¬óýzòŸË:‘?7Þ̾ßÊüûÃöûNäQÝô—;-àŸïÖ“Ç9-Àϳ–ûÓJâÛåø§õä‹¶ÿÃJj5$] )vvVK©»…E+ßÂÅÈðWi´ LÎ!u+Ié„]Œ÷)É.)È’ÐuËÂq—Åž´‰VR#­Ô†rXMÔqËÇ}Ê pvNέÄÃÛÕ"hIn5ý«ê9½Û­ôiIz.Æ)ÅÛDX-{@ÇÔ‚o¥_Sž’ˆtEF¿Š°û¤Ô)ã0¤rEÍT²¯KéÀ?Wù$t´‡Žû{õo”œ^àX“tTÅësk‰jt…Ïu§rLJ½”qótü>“·1©˜¿7 !ªb_UлØcŸÓG~ Q³dˆ:üîäs*~„œÉÏMrLÀBrëñåNÜ dU³ˆ×ƆÎÑ&{¤kø‘¯É],jŸJA$â/ÓHWxX¬Až üe6k?~^"åJñ,V?•,ÔC"v a~ÁböM.ãñ "àH„X>³_®Ï“ ±Z9A)—p‡¤üQkE½&æ`ÄÜ!`! ˜§O-Å‹9ƒ|ÚUwŽˆY}€=¥ŽMþ û%Ú£—ãSAEÜ'5!“;è…ÿš<ò )«á Â#êÔ’í¢E9!AWL]P ‡ Dø. ¥[/×IpZñ˜FŒMùdjþ°†?b‘OÈ™} :þˆKM6‰pñ¸UŠw«ˆ®§Ü¢W ýÀ]»Ï¥Gh= 9sÙ/YÈŸíW%Äóyÿßy«”X—Sö VJ–“åDÊB×2{´¬kõÓ%v«­@#g ¹ø=ÛñÊ\fŽÇÆï´Š'Rη"É 2á•¥ʵ™HÐÂ1+ ­²3Tè¸ÅŠåÍIÝ«ÆÎåÔÇ«Ñù¸µÕrlN[n­_Ü,>?Ê=ØŸ¬Ñp#TŽòsaÁf;8[¶‰—eÔžNɱZqÍG÷¦œ«`Ìç¤K%ä¨ãyq3w7YIŠrQ¾×4a׌¥ý¿…¦”ãê´O´1]:‚C;¶Óª)ålI_O+צì7:¡ÙœºúÍD‹j$g¢§["èSH‡ z¢ÅBS 8.¿O,ær…¼a…bB"Æ …C\°k·KK$kL튭SœnN×Í–m`¬W¸¹.5>bev.”Å b¾äe·Û›Õ⪬OÐL©j1ùLJÓˆ"Õèt©îl•ÌÙ˜<G½üˆ—óòkI5ÜøµÎ¦ô[S¾NÍsðiÝLÁ\O«;MçRݲ߉lµýðñ* e3£Y¨:KQÄm$'|(ê”Sêùº£‘Óo¶C uÇJËÛ™òÌ-³A¸D9¦L{#ÞK*Þ€^<±s© ü…A /@~À¶©ˆi¿Ügbq³[Ÿò ƒjb+(}±™Ý̨oUÍo–C‡äÉ´ãõ¼ïaÝö~%ö¶ýǃòïoÔþ|Üü¦ûÝ~þ»Ä__­üz'óË­Ô¿מ¶'Eݯ¶Rßo§!éŸÊoü`ÿ·3¯ÚþßîAÔlwÊÖ˜ªíæ/xÅÛYsË .Ž(IZZ¯ŽÖÓ³Œ\lÜÄ÷*H&Þ(Bíµ‰Æu@":¦M8æá½ÒÉ€‚œ³‰¼’É’ºœq¤Œì šâ”ÙÅ8·ïŽë¹ýFƧ&º6 VLº¬`ô¸T“^-Ñ¡œPЯŠr{ôÜA«tÜ*S³ŒkjV?4¬œ~MN¿ª`ö"¬>€Ip%:A¿†ß'gt©8ׄ¤ódÌ Æ0°?—ѯP‡ÿA@ü:`$Í4·fÒ«#ê…‹ k’ŒD-LƒxÈ¡Ä'œ\ ·úÝü‰¯9øsbòeöCÿ–„ù{뺂٭—`¨Ø üD+ê³"X“|Èk$ÆÝ,‚Ä´‹v5–E¼J'ö€ñ—‰ãéäÿ¨÷`Š,Ùò<¿ÒÚÎìn¿®'Jde&$$Z@¡µÖZk­µÖ‚ ‚ ­É’ÔUYõ´¶'z¦ÅÎÛ¶žÞsáÍ®Ùߎ?îׯ¾þó+ü>eR¦hä‰Uô×sÓ?Ä.ðŒÀêÌÏýdjò³é©âq£XÌ0øXì³Õ•§(¡—¾ÿ ÒüÜç+KѨ/Øa:qœËœã1瑇eó_€r‰k#Dì36}†‚üliö'E\ÚˆŒ}JÂ<¥`‡á—™3,ò¸„‹¢ÞÿÃkqú3:a„GŸÆ£ñi“À?fKz.bÎÉÐ_±#ø¥/åPyúñÊì騧¸Å/0³?„˜|…VpÑRŠ…abŸyõL -ŠÃ ‰£lìÓ€ž¦ç-ŠIcp:¸å«éR="kƔ͘ê²á®>rJWÜY@õ¸ ºoƒŠé²ë«†…üÐù÷ß6,<ìW)M+©þpT8¨XË?¶ çi)ªò[™ á‚V¶œývFÀÁQ Ñ6¥Q´åâ¯ñJFZ:¤ˆ¸…›5_³èˆù¥+Çafn6ÝçƒÈån8d'8´è°´6+ºó¯ž‘tm—ûþý\dÕ¿c"ÜRDØH*«q…OG€ã(=¨#ôò¦G¹FD4(hw‹êͤ¸le$ý’j£¨ê5Í]ßéNl£` Ú‘3ZHWñfíº5»v…Ky$fçb¼‹½`##ëæUÛEmÚAÉy™>í*wí -ÒªD;´kŤÂÐOe"”J¾¬Sa%Bû ×¤¥Š¹K 0í¨2ÖJh~Yu çüâ…‚‰ê.—œ~»û§!Èœ\(ŸšÐM>în;ѦWÚôHªNa@ŠmäE— ì—qO¯û‰oNʯû©´•«cÎ5C)»À«"~<©=ÂˆŽ ¥Œ´"’˶÷ýQæj+”õ Û‡KÉp*©f!Î-'ÅMœAÉÕ˘Sމƒâ,|.#ލ©SY;ÿÅVâõ^þ¨é=mùÏ7‚—ðe'rµy¯vt³¿lAow2ï™»^âý û~%÷ÝAôí^|8 ÷ýÌÇAöe3øí.r Üu"ßì¦AozÑ›vàõVäÛý $†¬¹í!ò¾Ÿø0H¾,ÙŽC1p äÛ^ìÛ½ äBI(ÿé ñƒ¡0{Õ ÁäÈs«>òÄ ù‡øý·Zï·Âßí"ãBòvÝ‘o‰ë¦ûuÇÿÓýôûû/ÓßÜßâx‹|¦ s‡9Æ> âw›ë¶÷²á|Ý ½Üð\4ƒŒvÑmûûYÝwÙ ”šV\ƒ¼õ¨êm°w VpE;lÉí‚¥“ÔßÏ[6C²¦^Æþéº|À¡Ãš{¯ìØ-ÙA04 ƒ’ά‚OPô áüAr·j¿ìEšîí¢ù°é¾Ü ï×;eëE7´™7ndu[%k¯ho&ôÍ„®•dRÁ„MçaÓµ[µ6ÜPm;¡ïfL½‚%ngúõÄNÎÐ-˜ ~á:œÈimÆÃ«D¤{ W1(ŽXh9Ÿ ëå÷J–Û‘jD’óp+IÄJöê±Ù­[-F„?;áBΪDÍ„¤ègGL„ZD\þI(7ŠÝœlPØÈh3A!ò²Š 6æfû­d ¦fV±Ó‚&R1$D »˜69:&½œjR|(à1àN&”)G$µ”*hc¸›f-–Tp:›ŠQ¹N¼¸žf`P¶òúõœ.â`‚ ai6 ÛXN Þg"åÃÂV^WO«³ñå ÕÌ“aÐÂt¨ !+£‘Qõ*æ­¢©•Û¤èfZSÉa€XŠQ©ßL`âfÖë«Ú‡ÝQµ¢d‚Èh9í¢¹™Õo7\›ËVÕËc’£Cvf=£ëÁÎjºÁÜR\°Òƒ÷£î¤ýÂÍŠ­S4Ãò¬g Õ„ ,$F]˜i&é²×DÑQbƸ”5)aŽËØS&Ù²E‰Q æ R´ˆ>& Œ ÛÄ@â’†! ¶gÎÏ4…Ö ç¡¨Ü$[•³g­*‚MMÔK°Bê Ÿ6£ä¯² ãBê´:©­©x‹Ræ,4À<™€Ü¥£˜dkQ§ÀobVº„[dà/i)c~áB?"»®Úâòýð¶hø¦iÿP³¼H)ÞW-¿Ý‰ü¶ýYÇÿ›íÈ¿\–ÿé<÷MÓúOç™ï;Îo×m:J€sUþá úÏ—ù³¸è§×ÀËüEÏû·WÕOÎ?î'>Tí×Ý·-ïuÎÔóð÷"ʶ[èá¢xb*Š 7*Z}œ#'M«(³Öõ¥ž9o, Wž*c6.ÚÄBéé³jò¤_Fˆji΢S¸Ñ¡ã&–K¾¦ã øØ§ÈMʸž‹ÒrçÝ*2Òñ9òš÷=ØÀÊ>|Š.¢Œ(˜ÓZÞœ’9#$ˆÈÏQ€pä¬)&ö1uù )sJ'D©¸s9¸™ÿ]űIçÍ¢eHÏôi0bâcÎê瘱ÿ¶ð™ž;í- p´ç!•¹òˆKxf¡Å4à¢Ga.À®RÚ(5LZü ~æ³Õ©ÿkuüÿ”2&~qsŸIãvVÅ¡¯~%¤<㑞šdh½x^Á™$£?àÁ¯<%¬a-Í~¾¼ð3 ÀƒÃ q#TÒ‰8†Å aVŸR)“òÌf”É@°˜~bõ5ˆJ™¡¦!2=ùcÐCƒFÏ…A?^]z„AMÀ>QcBînõéÒüç‹sŸ_ù€EÈ 3Ì Ž³) Â(`‡2ÁgÌ1O1¨ÏÁ ˜³ÈØ—¾þ\ "FΪ% 3 àÒÊ×Ì“ÅÉ€$<@M˜'\Ê8XÔç€C”å§$ôeFDŸSñÐÁrØÊ× V¸„Q&f¨RîÔáà¶¼•ˆ .¿ç]ßË~`PÑ]õ\»5C.^RÂËMúD嘶š2¥8ÆÚ]ƒ“rÆ…ô§*Ñaé¿P0ÿÐÈV‰RÊQú™Q&¬³`Æ©X,ù.5Æ(]ôÁ5ÐB0Æ$ÜRº$æÍ1È£LØ¿¤ 2fXÄD)8‹|ˆI°pÚpdè¸fÍÈSF¯6ã?‡mgPW ¨£#Å!]UQ'úiÓ_^÷}ÞüéAùûýÒnÊôé ˜0Ò¡×Ð’bf†W±–±s²NE°š÷)Íâ5“pµQç]ü„™êW®4B¼›Dú¨°…Y ¬R†[Åöi9a=×%%–}Š˜¾î—7CÊfHÑϘAe?o»ÞJ5üuÏaÃ{ÒòŸmá0o„ÎZÁÈ-+|¸€À9hUcv6+ÖzZ*ǤPOª2}FšGOq!¿g@%õ”¢•×nU­õ”.êàÆ‚|H‘É`­k)%,önÃÕLi.ÎNÝ‘ˆ !q#¥­'U& Ú£ÅCÒRX ™´—~Ìɱ©×àªqqâ>~5­í·<eËáV(êæxÍ”JJ‚¬°‹ éÒ`ãö¾Áæí-›È‹ÊÆZRÛ*Ì%)Ÿ h¡[ä«ú8õR´J0’q¦¥œ) kBÊžÔI—@|ê‹ðÔ®Å+4ì#&âw Z§vUÊÑ‹Qf9¸ˆOå’GÅ è2£4‚!Å¥Ìè¤D” 6¯z t³§ e¬9æÚ0`€2Ö(źõ41}Z#@k…ËJÆ´–4Úñ‹NÒú“Œö<£?ŒH.Rª÷5ëÏ;Þ‹˜ôeJù;äS¬ôŸ²ÿý¢ò›Ø÷®Ë„ô7ýà§uÛǺå×Ûþ·eÃE\ÉoÖ=/ó×ýÀa€óË-ß¼n|j9Öñ}ßò^¥´Wiíûºû,­ë¸øè ûeyË'Âèh3 „÷œƒ~jæ-[ù+€@*Ò„†2edÌû¤x¿œ ¥Î€x¬\´™½ Z‚2P€½øH͘Q1g¡¿¯d̨YsÐë—Ç^xkì•Ç„fÑ­"‚…bìS›«æÍ )£<â0—0ÓÆT÷¿›G~äDzFAþPö5÷„yD^ø¡ðÈ"œ)¨O…ø¯Ô!-{L€ûRLøZ€ûJÇžtÉÐ媜ò šWuÜ,YqiIF骔1Å'*9s Ÿ‘ úKÖòEöý7Ú_P?ÁÏÿˆGÁÍýºüâä¥Ï•œè23×QÐ?Vóg@LdD”q*~Œ¼6 ÈAÂÒÉ“âøÂÜçxHâGÉä ujý5ní™X¸F"Lñ€:@>xÜ8fu‡}¾´ðxuy˜Lš† jþh~öË™é-Î"÷sp@SèÇ@;à¯.} >à p‹6Ãg/°é³Tâ8<Ác¡xè„1*n”‡ Ñ3ɦL‘±Ï qDÏüXÎ_sÌy…pÕ¤&;Ml9-bÎðéS,â(mmˆŒy`C@?šý¯Tä^_è¸a!6é9“0º:÷#òò›0! ÏÐ1#"ÚŒ€2e’àD”ifˆ=hûa÷×oúëICØÌt©pA1¬'›øÈÍ=3a·ì^iow2oö á’K¾jÌëØ§m÷»£äû“ôß~{À“usÂfšSŽiÅÕ¯¶Ã¿¿©ßöBa#ÁªXs›éñ $ŸP»Íd½UJ(jMÜÃ.Æ¥…˜$줇ítd”ã¬:ëç6Ó€(<½pÚ,ñV*f'×⬟U sã’’=ìÓ¯tòòËïË=ÿåŽûlÛ¹ß2Ât¿ uàÃnZÌωùy&5l4ŒßÎ2«ñ ÁØzÞ|¼“lúp-µ’kyu1!iæÕÙg"KYÓ6¡ž6'ÜâRL]ŒÈË y!*Ünš:4òe% ‹ÁÞ*è~ùªñÍy¾VÅ­h>.·ÃqƒŽùz^rB椘;#¢ôjœ^µ¦/ëÕ$1ͤN 9( {^L›Ó'¶s†²——0õôçe°—³ú´t:ú©€2®àÎEmì°™!'OhiÓ›qý_ßîþázëgë!9á¢2s‚ZÊ~Ý íL[H·óöŒKX ë¢~Î++ùe;·WmÆ•%7òùLÑÇ×°§bVN-bökq“$k——½j„˜³vÓ¶nD·P´ƒÊ³šw/k?©ún»É“ºPç¸å?mÎ;á‹Í0bÛáËNøº¿í%®Àßßõ’oúiP °Í7È=œì»íàI¿ïg ÁïËPàÁBäU'òqy‹ÜºAhçÛý,Xð_m†ï'þ®Ÿx0 @QüõVÈçÝvü!÷äfN rA{Û A.”Aàç~Ú÷÷ƒ?$^#ÿº ¿G^Ô‰\·|¯Ú»ÉëuïòC ¨0vUGÞã4z¿Œ\ôùTùÒ­å‡bWM÷eÃ}× ßlNëÎó¦û d>ªØ@—-ÿNÖÔO›+î‹fà¨ê>©{Ï×çtÝðb9ÝÂf´ÑaËç<5JÉpéð Η4l–c$¬I'ÆXUd%#f-šUT«†m„^Œqj) æxD´)9!ˆ­‹ØcW“´\kæ³ËŠý²`Dd·ûUNwW6ÿlÓÿmÃ~“Q¿+›ÿr”þõvô·ÛØ×#PÐ;Íÿ~(úÕVà›ºýCÕò‡ÝøÏÚž·%ã¿\wÝìïÖÿq×zS4ý|3ð¶l¹LªÎ“ê—9ÓN@Ü´²ÓƆSP¶óãjªÿ\°<¤¥Ïk VFÇ@)É“bܨxmDðÆBØ(9qÜÌ]2óÐ6ÁŠ–>+Ä +È:2bŽR³ç˜Á Ý¿Ë:ôÀ?"âs !aTL×°Q_D~.€–wåKâ¨Ë_°ñOùäyø,`8tÌ# yigˆ‹Ì^ý‘ŽýÜ,˜Ò²GòE‡lP‡‹ù‰Š>jàM¹å+ОZ`?rg ÜYù¹š3ïPù0#ò˜†¿(eL³Ö†ì9uùæ±2 ÄÆ ag>›ö_¸„þDè ¬!6 Ïhø(hD´Q`NÆšñV¸ô9:q‚Œ¥àŸ3(S\æcNÈBY´4HòèÓ¤•'@5BÆŒ€>Í£NhÄ«*Ñ2$ø‘û÷Ÿ‡¸” 0*}Z+Áópˆ)Þ,#qð̵Ì̘aüÜOÄ”Is®”Ÿvco«9Ÿ€§RèØ³|ì»dÅÀA%mÜ·%‹`ÙÄC8®æÝ"Á"\°‰TŒQŸó»·­?¾]ÿÓ7ÝAÕf-Ú$+%¿¨›TVL'3j&·‹¶°›oRã³qU2 Úi!=îãìäRBvÒ<ƵlˆwØv6Ó²v^y´aé•T!ãJÄ„YOÊAf+)Ù.jÖ“âݪáxÃÖÍÉš¦›ïªçºØrÞí‡>œÅ^o÷}ïO£¯ÒÈrBÜlLbÒbøÌqƒ s±ç¼®ÍH+À%½ê(å ¡ßdÀûœôjZQˆˆ*ÈowxÙ€¢V›e8§†­ÀNÓÙ*hvÖ­õŒ4á¥oU G÷Í^ô/ßþé§»¿|Õ¼ÙOî5]µ„B'B‰èãz°J)ZT‰—`¾fI¯ÂIEKNG§&‹…hþýóJ« ¶2¾»ªþæ®õÝYq+­.¼OMÆ|íñ²OOÛ¹ †½ø•`åIÞ!øËûýÿå #c.ibɈc&ÎÂFB±Ù/ÚJ¶¢-jdt³¶ƒzp»èpÉp%þ¸êî¦p€ˆÀ>ÒÃÀÎ %oÚ!°ÀB0ÉkäÞÎß§Úy½ ‚—M÷õ†ïÝò\ ü+dE×]7ôj+tVµŸVí7mÿ5ò³oä‹þ›–ïzÝ÷²ù@;.ÐyÃõbÃw±î¹Þ žÔg lÞÓºç¡à‹vä´á;©ûÀîg!Ð~Õz¸3ÀsÐðB˜šg¿æòéÃ‘bìDpêÀž­œ,äö ¶ãvød#´]vö‹ŽÍ¼u=¡«ÇµïW\à—BŠnÖ²×ð픜ݬ©…|¦D8ªîÛ/{%W?gjê¦õ[9ÃAͶWµöó,í]Y·(åÖSºZR²13~QiÜÅ; ÷îºà§C’ 7/åðøŒ”ZLËÐLêªQUÖ+ ›é…€¤Õ»Õ8ŸŽàדFdÄcä‹-/7åá„Ì䔼|ÌÀã1àƒVJ>, Ùh ˜‹u2€j)U9.{!`¯zz)&+F¥I·šTöj¶V^—ðˆû ;ø~ Î&—‰ž ÉÚUgÈÎvêÈ™¤WcòR\‘òñA09Ò¥J«“n.œ\&ƦĖãŠbTRMÊ`©9îÆ‘#ª¤Wd‘c ˜'îâEœ\@ åP|&:àM!ª,„Õ”n»æ®&u?n¥Uµ –fÖ°Usî¬{lš9c¿é¿U0í4=Ȱ‡Aq)®j-[Ïè` áÊ«–Ež”ñšY]»h€…¹¿ß%‚-ÀãÒá€|€µÂy.ñ©U…౩ÖôȰ93:ñ\yT¼9‚¯ ÔQ(v°¡Œ×D®DeNõŠŠ7 ›4h¥™åËZÑRÀÊTñ`ÓÙÔ³‚(ã¡y\¶YEщ0&ÙšN´ªæ£|$Œ6~TÉ]àQñ! dUÜzºN´ÂÁ>¥Oýà¢b?HhzAÑû–ïÛ¶ïçÛá Çoûáû‡YỪünøõvô¾ÛúÃ^ú7½èo¶Âÿùºóû~웪ýÛªã*ª8ðð~¾áûP¶}×pýõ0[•¯Þeõÿy×¾Éè`³¨ì&§]µEd[þ–OtÍŠSuò+N‘;ÌYxl`\2’ž½„#'MÏØ¸$k*èàs­üeptÌycNŠ8dd/ AúŒŒ2¡¼¿É¨#À#x¾„gnúsÔÌ—`×VGp˜Q %âaøœe#ä­â1# òÌðÏÕç@RÈ]ÚÁi`†nò@’„%®H¸K"ŠGŸ•òÐê4ëþ&\Ú ƒ8I êKâêD  Ÿ1CÁ —¿È‘°ç)˜§À6tü3ÒÊc‹ø˜‡Ezh¸a¥ ð#a¢ŒR‚SKSòÐRúmeHJ›¦.=bЮ ËiÓa«›³æ½’ͬœ„•##Y…ËN)¶T~æ¤ìÉמØx‹·½Ôæ‚Z~Ò*ÏØ•>=k•ĵ¬ºG¶ŸµŸ]- €§Ñdm'%×ÍFì²ȹèFxÀ¹ÙIð¼ÜŠßçïÚŒßõRï¹÷»ùï+ŸËßì>ð r×nþM/õº—¼ÛJ¼ÙNƒó¶Ÿy·“bßäï¶â7÷_µƒ®Ûaðßôÿæ½ü»6BYvaÂäífäåFìÝVôU7Îífø ¾FÀ Ø&~Ý ¢\µ<ÇU;Ø×ýØë^ä¼é¾Ù ¼Ü ¼d?ö¢å½lyo7ƒ 7½È›­È›.¢»nøA·ÝÐIÅÅ®€ˆ¶Âgën8È뎓ºëe'|Ñ ¼êë&.ZÁóõÀY3°_r^´Ã§­À^Õ5(;Ž×ý§A £uÿ~ݳWsíìV];%ûNÙþòÜÊ‘£¦ ·lå-0a'cª9h…v*žvÎÚ+9{eW+mªÅµ6ï“UcÚAÍ»Ûðo½²³Ðn$Tƒ¼í°æ;(y€yËÞ^ÎÒ‰kÚ õ `”LäfïAÙ]ôJw6†VÞ´ž5öjÎnÙq°=r%¡.Däù°,’­ ¿‰öð%uÖ#)ä9¯´R&‚ˆ…ÕÊwªÎ¤“µ²b6VÚÍÅìô¤““Š\¬ÇH€Ö¼‘ä¸õxžˆ“á7“À–rà šlP˜ˆ¡àMÔÉ XÈ6ÕªYކòÀK[I( @¨™ÕBIhÐ9S&¢ÌDÍ¢Õc¤YÕø¨‡r²*€ 9}!"¢€ú¡Â°•špqêR^QÚ'ê”ÌPÔ™ ~R^aÆ/YÏš·*®zÚPK©Ð€:¢.žSOñé`]:JÔ-ZÙpIiå­œy³ìÌGUñY˜Í¬y¯ìVí’¹Wwt«¶FV—HóY.,é7]lŒ³b…bp Z©q73‚ ¿[±l–°´ÐËø ëþ9 ²ZŒV4'¢=3ÊÐ2Ö”SGü;êðçÁjïÒQp²*±Èo}¼«j-`¥Å Xq«rE/FðäBb¯‰¦,襫&ÅšV„Qp—¸”ie×1¬šAÁeRLŸQñ–ØaÊòcà à“ Ap@2Ö<7Ì[øü²ê¼­»/K–—Eë˼áýÈmÞðǽÄïÑweó¯{¡?fÿzRø·[¿$¿­{~Ú ü÷ÓòÇš ô‡Aúû–ÿeJ÷¡ê¼Jh ÷¯ÇŦ–xÖÿíºõ:oùE7t“Ó}lºÞ5œ/sÆK?(«ÙXe+'oaå­<-mZŽŸ'•Ô-…üZbuHIž9%k ¬U°"#ŒA–’2´##Ž›yh Í^yÌų1O×&ÿÀ|vòá­ øh ¥ Ïhh+Eä1}.øÊ3€Oá 4¾ˆò˜¹öˆý ,dIc"Úsá±A8ãT.º”KFáT@¿æÑ¬ŠIu¼IÞÚ—bÒ‹eäϪYrÚ¨†5ÅÇky(MÇ ¹N Ñ,êy \Â3âÂOX¸!ÒÒ,Ü=ô²‘/n–îð™(¥«`„¬> %á-+DX•OEÞ4žìA£¾"âFÁ¡ º¦Ô9~’I›GÍ~…A¯¢‡Hø @ ìÊ3<æ9d±è(w…Fš"­!·ŒÈøçøû‡e€4RçÚ< bÑf„@È%ú,0 hSŒå3渴YÀˆà—ŸÐ c«¨/I˜a‚¤\„‚;מéXó{UG@‹¿$¤Ä'¸ìŸ>mÿá}¯èÁU:¤'5‚ÂÍ„ü²íe¢GHÒ/‰x„V-Q-˜³¨V]:wöåIzkÝÑmZ×KÚl˜ûݫʻËÔåÀ{´azÑw^ïºO6Œ§ë¦³ ëÙºh§›’î•4—mûu×}±a¹êØ_õ½wÏISßM ûyéQM½WU½=Ž]nû^ôƒoO³/K{MW9ª<êF›YÓÕ^v³âHúDv-É®#¤#ÒFN»UµTãR%k’‹‚­Ûį£A·7l¡%ݬ­’aoÝÖLË÷7œ/Çg $œ¬ZBÕ¯Ú/ú±ºÓ$C³ðOÔB”QŽ‘óQî¬V¾*`ÏðXS`•Òe…t…Ï#Ÿñ8³R1šËš²§%ÌI£p¡àåÜ<—tY͘ ¨-Ù«£ML–R )KaeÒ!Hظ> 9l`HÏ-‚e9yNa»3°º)ËùFäº:­9Öò˜ž²Wõl¦ÍУß-ØÛaÅIÅqZuf ô¢MpÞ »¥$¿†e—Ñ“e%l j9I‹(fä´Âšýœã¨àè†5m¿ò¢êÛËZ!yÝŽ]4Ã/·§0‹vPÎy;|½Üá¹îÆ^m§îúé›nüÏnîíNöã Nxæm?}oçÍvê!ùº—útXz·“]·£7Øí&XÀ•(ÐΫn À沉Œg> ôÿѼ˜Ú9¯»Á¾êB$ú¶Ÿ€ø=ù„!y·ÓBµáíàM7|Út휷<@5§Mןçv;þeÛÝ ]o…ˆ^vƒ@2 äÅ¡^ìíV8çh3t³jºlº_‚Ýð]µý' 'L ^lø/7‚§uÿ‹v€çE'úr3:©ûÎ6B{U÷nÅyÔôŸµÃ`[ šÃ¦¯_²ÃÉ>![u3‚QÓÔŒiº93B>E;pÎVÉÙÉÛ;yÛ^3`ÓÎÙš)X@RXóÛL™5Ï^Õ9(YŽªž£š¯àÚ¯x/Ö#§­`7¥àé$”í”j+eØ-9öŠÎ„ª¼É$í¢¥–çBÒ„›o }F*$k)m)ª ZlåL€ Ð?J¹ÄI‡0ï—%ìü˜ÓHèÊae)¤Èû%¥¬SV"r`ž”‹›ó ý¢ÏDйX)ä6‹$`¡„l4Àhþþ÷RÜ´Ÿ_M*! ”bÒL@*Çå@v5Æ¡Á@™ˆƒ‘ #=ur!!(îáFÝü|\ó “AiÜ+,ÄTõœ¡ˆ¼ L̵ò€sú°s0ëIu»`YÏÁ¶‹¦zZ•”bм9æä悲z PGŸG¾¢’WSjP=kti¹ˆ®ca§ ž5UR†fÞ’jâ^Q&¤Œy„—ünÕsØUSÚ­šý±=lgZU˜°ƒÎκû¸´<õŒ6–ùìu¼¹°0f¼jJÝ´vQßȨªIy=­'åãØ5«V%€ç^k&ù²”5e’¯j„ €:2ö4$²@!m (Èm ù-t»~ÈÊ´HÑ  è€'‚,Ë(ÃèÄh‹¯,JX³:1VÆEC›hVQìz:—4¦ä.J³FéšÛ@ìˆE­çšKG³©H ŽSKÕ W„¤çòµ¡ó²ý];ôªé¾ÈÏÓš_ô£o*–ßïÆþ°ÿ®åþU/ô»Aâ÷{©»^ÿËiéûvàÏG…ÿü0øœªëûªó®h{™ÖZ÷ŸÅ”޿ݶ_¤ ÿçËÆÇºû—½0òæOÇÿºj{×@f±nçÔì옦dã&t4þ¹š ó\JœÔ±„ØgÀ3^ñïØC™‚ &8@;ÐUÔÐfå$ £quZCŸUÓg-Â=oI‰ÜáAºÿàC+À]æ ¯|ŒØ¤k-á äü%6i ø‚B&”œ’óeܳ’ æ/É™ :Á ûLΘݮxkqö,oAÇA]tã×;™jDù‹ÛÎÉzèE/åS“¹+O£FN@C÷«éµN€c.<ùt¾±•³A³[ðŠeûû“‚Y0«¥¼Û‹8.dœÜÛÝl;¥{·Ÿ:¬˜¿?/IHO©K_Bß'êÅ<‚ˆ“e×àª%K$,QHÑ^7[)]² 6ÞªÃöš¶ƒ®÷ÕYöb7|Ú÷½ØŸo{¾¹Ì¼9ˆ¼;ˆ¾;ŒÜîø_l¹oúÞ×»w‡ÐæºwJòÛ÷7§Ñ£¦ö²ky”G C¿ Ú¯èOÛÎo/ò?{QíŒq í¢ê—,/ú‘‹~¸™V‚N6½Õ˜d#«Éyyjάœ:¥á,˜耑·Ñ›)åFZ´S Ž;ÞjDæT­qqOH‹?!-üˆ‚þ‚¶ò÷T-X ;¹pUTKРѢÓD°¦ÈkÉø§2Ñ‚ÓÂPHÐBÞœR¶¢–c€‚8”çês%sÊ*Z LþƒE€jD›“„ôÜ¥&Æl\³ãÓÓ›<±ˆ4FG?jÄt͸Þ)ÇùµÔ€–Ñ1ä©¢GzÛKßu£yÓAÉ6È[/Ú‘ZHÑNèª~I?¥;©Ø_4ýGEÇ^Îq×Ë•\ª°^±Éãu)jÛ„q»(¨c6#º£ªïE;zÚð—%çQÅs·¾†c£Ÿ:E©_ôâ p )¿é§çYÝø›AöÝ^þv+ù<¯ûi°ov²oú°à?$¡ªûÅ÷»y(öýIí=@Ñ~ñe'v½‰x;ÈÁ$P ’¼l…| ÐC˜ê›Ãâ‹NèͰVä´á¹hi¯ÚHÞt#öÕvü®¿íÅ ë¼é}=Hž4½‡UçÙ†ÿlÝ»[¶×]·;‰‹vt Ú扼uÃ/6C/ÛÁkÀ¤vèAÀ“N —›a¨ór™ê¤á†I.:AðÏš>ØVÍ ,6l1Ø2gÍÀI3°[vÛ¯€vM×­ö›¾‹^¼_q¶Ò†Ýº§_uvræcž­Ø æÞkz!ØLé6ó–ÃÀ æ=é&;ñ^Õ¿Yrí4B;À >í¥ÁnäìÕ„ â»ÍH;ï¨%ÍÛ÷nÕ¹[±¬NZáÝ’ç æ¿ÚLÀÜ%ÛvÁ´™”Òí–ƒ²³ŸwìV=.5éáçÝGÝH!""í¾Ô©%ùL´€™Þ-Ûk)]ÈÆ»ž3ÇíüœO1³óY5ªN8ùY¯$ãF¬Ì|@\«AÕ¨¢–¤Üœ Žƒ™ ‰ªÈã*hÄÕIdH=D¼&Â-aqÂË…¬ZZ ¶]4Bˆ”ârà"( ´×ö‚…pÐÎz¹#>r“'&¸øÅ¤.êzmL“ëÔ‘}VºI‰É†$¬®†¼´¬ÍG$P[ÚË+„$6A+3íÅ\œR –Mò ›C»h+EÕ'`aDìlHÚ.;Ã.a§êK‡U¤Yt”R¦ZÎjÓÑL*bÄ-)¥Œíª·]õl¯‡7«®t@¼Q0uJ¨œ”ùhù¡9s»îÜ,[›9=$½FR>"mæt@8E¨W·€­$eÍœ” ÀO¸^Þ©ÃX”Ká´Z0 õ¨ùÈ@¥jÞ¼ˆ6& <—³g¨Ë_Â~Ì$‡\XMX ³c‘-LT³|æ;T†Œ.ˆrêH&ùš‚· eÏ[Õ$£‚¨,›”ˆÃ'OxL÷Hª)SÀʵ*‰tëé €GÎFÅ\"EA°qæxNs¦Ã´öýFàû­Ð¯wßµ}ÿãºþo/ªÿÏmô»Ýä¯û±yÑü·›ÖeZ÷톜_ô"¯JÖË´æ<¡~U2ß•lgq廚ó¯g¥7UÇwmÿŸNŠß¬{~Õüñ0¾©;ÞT‡qu×'îe» ]Ý#N™üå'ê Â3Ì-!ŽÙD˜r@ÁÇ «³:ö‚’6 VN™‚Þ¢‚:Ž7Â]}Êà £e£´Ü« lãÕRï g‡‘Ѧ€‚Œ‚eæòcúkð™+O¸¸a“m,©‘;'“ z¯ãÀ?"Úsö8€@`!®-Å -Æ*G™$³Zþ ñ¨Sµ¬åM‰ÈOù„¯mr´KÕðÐFl²‡£æÎYXØ­°§„Ô1yv®„1‰›ÿáÃ0†„=T£,Ü¿À3ÂÀ<"-~Á 1±O‘gmŒIuĦÂHclü”¨`a‹cT(¼áR'‘·tÈã «.íP±C¤Õ'Ç.|! N)8KLÌHÚ'¯&ŒBÒ7ÒCܘÍâU£ÍÃ]ïd¿½¨»•¸ë~ò»Ëæ¿þâ2 ¥Šñã>Õ"ÀÆŒ¼­¬; a8Ë¿yµ³_ ÈÉ ¶•ÔþùÓþ«ÝÄûÝø‡ýdÑÃ·Š–®wRéU?úÓóÂÛ½$óåÚôg^#`À¬Äeý¢R\éwóÓI^ƒx»”3&£òTDfP­Æüüۋ»—Õ˃ä~×›ðqbnÆñ†ãhÝzÕó\ïøÎ:öýºñ a¼Ür\ïx{9è´m>ëX_î8^ú_l;owýÝœìzÇÿýUþv~sx{ºèúû¶sÖñÕ"âvZy½|±Èûõ¸ø¸íº;H}º¬¶’ºJHÙ+8¢fVÄÄÉÕ¨Ø._,»5Ûe/¼Sq¶GpÜÍ·©pp¼AßʬÀÑ9õ‡Ž,‡ ‘h)êéd«愘3ëµ³ ,›üœI‘ j@;’Eµæ1PZ)]Å/QQÆËAÊÂ,y„rò󔃷rL¢Õ”G\éF¦ŠƒR³QÐÄ´Òæˆ…“t¬"LÜÄá Ÿd¬üëNâ´â<.ÛÎê¨$çàoç­—!-u<¤Äì¤t—uÏVL[°NªáFÀhäâÊq{À! 9$nÛk`YĘ‚_¶_÷ôÒÆ‚KµqŽ*®ý’ó â~щµBwû¹Ë­ØÅfôf' û÷¼¹ìDïv³×È <±·»¹ÅW½àAxæÃ~ Í·GD€g¾?­C亃$?WßÜ¿óÎÏΛßÔ øºŸÈǃøíl%"®ÿ½æÙÏUÝ7[Ñ—ðiÝøÎU;(Îõføúx^ï$Á6=¯÷Ò—ÐQÝ}PsÖ] ã¦"¿ÛMù¼ÚI¾ÙÏ€ Œ´~·“z‡¼xÓ‹¿ÚŠÝnF®vîu»»ì¡ÌņÿÕN*9niÝÀsÖ€%‰]¶#°Ñ@ÇußQÝ·Stœ´‚—ÝØU/~;ÈœoF›þó^ìõQ¨¦‘Ôí¬Õ˜j¯á=j‡Àç¸Þ*Úz%ûÑFpP÷í6‚»ë‘~=Ú®Ö³öFÚºÓïoÄZ9G.¨®§¬íø~+Ö-{·+žz\ÓÉêŽZèÈ6ƒ ‹nöà~ݽßpíT¬Mçe/K²]„ã9”õK[ys¿îÙmù a¹ßLOxÈ,¯¬U¹–ò‰‡YA§h J8øqÏ*Åäý’AÝSÊŠ!ùvÅn-&]ìjL^‰ AQ%*‰;ñœG‡+D¥Ýе•×oŒ@8Iä‹H^ÂÃidµ’©“?DR~~ÌÍÈqép e`ªÃM?4Ö½º½Ž,ÿtÊf¨3–4ó–\L"Oµ4"´Ë@m-夦–Ö¶ ¦jRö ýf2ÔÜÎ*1 &€.a!¢ì©§õ÷o àt†µëU•„8k]Š)Zs%iLU½f(àøí‚bÒt³Q]>¦9Eé°ºžwìlD·ÁNÍ…;{»h^ϼF2h§‘­ô¦ô@A;ëžr€Mß*‡}1q§bÜïxYN%)/'¤'åç¬$K5ɬªåí ûØ FÃÞyøŠG'\ðÆ­#äƒü`ô>7jg†­t»y¼¥,@[iUá$Ìi»–dRàT´NŒ‘q—„Ì9 g†‘s’^©ÏÄò™IŸ PG+\‘±æœ—ŽæÐR¥Ì9Ȳ«ÉjZL×QÆßnEß´ûIõÛußÛ¦ëSÇÿ¯×õ¾,¿áùýnü¿~µûãAö_¯×a>µýZî?ç~{úý~êCËs‘T]d4¿ÛOþá8û׳âŸÎ ¯+Öïºßî%o Æo[®Ÿuý×ÝïšÎWë›uÏmݕՑŽòæ†Oœ53¤1+-%ŒËISF°6TÔÑSŸÙÄ—/&Œyh`5cÎÈ]‚ Â?äI%uZÇYP±æE¤q1i\H“Q§uÀÇ!ÇÃõ6á@³ã×Ó¥Ô)Q/\Trg'€ju4‚y9gŠC’2',ùägwêða;€Ç(ž1Kçœj4ÐŽA8ë7àèË?ÂNý€´ð« í7"”„:b,hxs<^#ÙÅkOx$€™¯±³ÿ´ÃÆ ©xóbú„‚3 @+¢ŽCЗKå‘€žÓV¿Àî!÷f=z²KG°ª0Bê(sík“œ ´Ã¥Ìj%9oЬ¦ 9Kr–ÏZs—9Ìyà`³ži7ñÍ:ŽIË–‰ð 1CÁO‰¸ÒÚ84CÆM’pÀÀ ÐÎCÈ} °@;€@çÍ L…¡  ÑCU€/ÚQ  Ÿ»í4Ø›n  8`æ¬é=ª:k®Ó†Ìº\¤À¿lÏ‘†ƒç›aÐIËÐð€s·—ÿf'y3H½ÜN€½êÅ.º‘»ÝÌåFð¢éÙòɼßÍÞõ°îú©³¦ïª¹ÝN¼ìÅ‘:7‚WÝ諬{þj#Höb3vÕ‰Þl%®ÚÑ›íÔÙFèd#¸Wsƒ…MŒ1¨¸€j¶+N@½¦ïA­Àq;ÔΚºEÛpN;Xªjq ZÁNÞ¨³ÛŒlU|Ý’üA3¼] –¢úbD—òÊk 3r‡§à®§,‡›ÑnÉVOªÛÁ“nl»ê9í&îŽÊïΫ‡í´ø½ªùµSÞ”J£vîzÆ´ ³(Ù\Z`@> ì!ÌB–ƒM'?n~>$ÛÈ[2~ñzÎh¶0m²Õ¨•Y‹«òq>(ªÅù22C1$B~e…ÜU•£âŒ—“ñs»eS3«­¥Tùˆ$áåfƒ"Äñpj)./Fe~ %ê„¶•ç3‘ÀÉ&:îæÀ$ëy}%¡ìV­¥˜¬–VÃäí¢q»á¸¿Û£=oÌ„äÙ¨j½dtB¥„66ó¦tPÒ­ é +^#£-'I7'o…+  N»`Š Z˜ÈÊêɇ›á­*œÔv†l¬RL«vpksÈ%–ó—mZZ%ce"ZPØ-näÝz"Q¯ .ËqŸ¼]vu#›EÓ:Ì1*+E¤ÍŒÖ£ÃïÔÇ›­²6W+«ƒ±‹|ì Ìúµ”r=¯Íyµ´b·íê” À<å$ÁɆønÚׄÚ1 ÃçNkùsÿ/SïÁ丑%êþ¯÷îÞÝ‘F®[Ý­6ÕU]ÞUd‘EO‚=h@oAzï½-–÷Þtµ7jI-¤Ñ˜Ý/vÖ\÷ÞAÕ¾qâÄÁɃ™_&™â…ÛÅŒ]=‡Ë&Qö7ò¥ûv M/Up(¹ #+`¦æ™×CílB'ËQ-üw×?“…ËgTÂ(”ÜFvÄ#uãKͼB45ˆN>«–L:u,ÒNM«ë¤ÓFùʶ¨‚vqØ%uC½Ï¾ù’‡G»£`?ÔÌß¹îá1ïeŒ—×Ó²íMËûËzìŸö²¯*¶mï_¶SÙÉþe7ûÛVæçõØaLóËFôŸ²?®†Þ5ÝW%óeÎø´€ÿi;yšÑ½m¸ õ—õÈoÛ‰÷-÷ùµçר›¦ë}Ïßs N –ó²cRm¥ñALWpˆes·ÔŒû’™[É”[µ@{øKçï&ˆ–; ¢á<Ö€æ«Ø¬²9ó>{ôpºTóøu@J¬‡€7¹ۂ™¯T|Í?’ù»9lÑ«c%“0rqdJÉ2 £‚…/üû:dX)xÀ£}Æÿ/°©¡€?XYpà wµSG³¨'mÊq‹b„0ÎBEFZ˜nl>Љ&=ƒä1.MyE&Ù(ˆU1eSÍèÄ£:„ZìC̼Úú8‡=ý{Öä'¬ëõ1A€Z©5ÙGÿ~êáÿ› Þ0Ÿv[ĸƒ°¾Ðoói· U¸pWLªÆdœotRºZ4ò'Lj–B8Åž»'Làj– [’ '%ü1•tN£˜ÇuK[%ßv€™½Ú…ˆ™Óˆik¤¼êý|Ry³Óó†QÖƒÔCòËòíAÎ z$˜»õHŸîWW“ý‚-ë“%|²vÑ™‹` RvKâyÀ.p™–’¤ª˜2F)&Ÿׯ÷Z–"nN.$î• Ý"¶ÞÒÀúeýóÃøù6y°ê>Ûò¯·L§[¾zÆ1iÊ‚iåCJä¾ZúH)~PIkŸåâ>¡U;™ð /vãþ~ðÏÜþÓ‡ÎÁÀUOÉ÷û”ÒI—έŸ³ JŽ^ÖÔÎjQU%ª¨Däëug'mÈøPÒÄ3I§#vQÄ)Î’êz ·iè˜t šŠ§×ù,l§~ÞcZ4«g ò1\5©“ŽÙ Å1Ég¼&vÈ)ÌÊ”_± Z1C=¨I›MŸ²Ö¶üòA+Ô÷Tq§dµì=èE{›GÈÙÄ•0Vô©Û Ó ãØ«“U3pFT ÷•ô{ƒ˜á'õã6Ęq«`«â9hùz ¬î—ž·ý%g çØkiO=hñéE‡Üi;|;ÆX„Vùl™TïÕˆµ¬9‚1šAåVζž²ì]çíÐnÅs:ˆƒõ"p·\®¥Îñ“^ôùVîêôåøÕj äÙJêùÚÍ««Ô»Ýò‹µì«ëî—ë90@€U~8j|{ý2 xæíväæ-h@¦ƒšb®©)a¯Š6óõvŒzá•?ò¿\Ž=[_QÃÄ"79`ßtì€çÚ»éá9ë’ÔK«^`æ˜nFîµü`?]OíÔ‰SˆP½`€ó ¼ ²ŠœS_ Gž×QŸ*QÀs öZ b€y ÛãëÁk9»â 8ïÅÀ<ëA@d¯8è„€s@?]OƒÞiÛ»Jv€g>ðì÷Âë5/Ø›ußñ ÞÍY|ޱÝvh­ê_oëð âë½ý’wPñƒ ÌSOÙ¢.yʧ^©ÁSMXÛyÇZÃÓ*Æøöñín¤ž±¦üÊfβ?ˆì¯†—+66“òKó aâ¸ô‹¥˜¾WrBý^K‹m܃ĽҨ[ w5Tý—˜ >WF’>ppBÎHy7gêäp0ê M;£‡ÒÛoZ(Eäí¬¾‘RƒÔ“ªzJ ´³Õöô+¶zZŸ HýV¨L9Q h[Šâ"õ91<H=Cõu€‘!e5¥ jÎJƒ$jU ž/ ÐJÝ ¹­5ÜkM M†úbY‹ë*ÀO܇–S†LX• +I'? ªNˆ„CÔ H1¦)Ç1hDÃà 2\@:gÉIA'VÃ&üö„OÀ“c¤]LZÂÔiäšµŒ\ÔXÏ;ë9g>nÌD ‰ t«àˆyÄpjM¢5G-¤.Îe R¼‘ÒÃf=‰-—lëu7ݾÑötŠx)®iæôŘ<êá¥b“j̪™„Í€mÑ¡›ÑˆJ9·õÒ«r˜åÓP3j…#Jî#ÐàпRó‡Lò)ž8¶Q:áÄæAâð?:ÅNŒá1°€yŒò‰cTÌêÑi0ì6T(%°Ç¤ [Õ \NÓŠ'vôÒ•`ÌrýV˱<8À¯Œ"¬GrÖƒ€Šþ¼:):‹¶Wíà«–÷UÓómÇ÷çíÔû¦ç—µÈ?Ÿ”ÛJýº‘ü—‹ÖÖ9ˆ(¿ïÿ¸Ÿþu#òã ð®á<Š+wIéó’é"«{Q6ÿa•|ßr}\öÿ·ƒì‡¶ç¯{žo{¾íˆâ8o:Ê™V"êAD³š4T<2ñÔgrú×(ý®A0¦`>€Ó"ñ–”¬G â¹Ûê¥!2¡ßÎÜ D†`úKH•^÷ífnäèEã€7˜` ›‚îÖ2Åô»ü™¯0Á˜C5Ï›þRH¿‰†m 膌¨Eä¼û‚ù/8s¿gMÿN-|dÓÎ ¬¯EŒ¯\ºgÐ'嘂sÛ…My 3ù˜šwOŽ«ÜÑ?ó›q—À¡žAYw¦E?ÎRóÉ9õÒq‘eD'%‹w×+Iñh_ªÃ*Ác )û>gö ÎÌçŒñßA€‚÷˜O¿½4ósâÓ™Gÿ…6ò_YS¿‡T9÷¾‚ÿCž˜UÓ þ¤J8e/xL"½|Ïx$d [uð¿ó1ù¼L4©’Ñ0%ײm8ß æp™#|Ö¨„7)æN*$t¹xl•tA.¦‰9$⌃‘ §8Ì!@6ã@Ž™‘Š&jr` ¹tV…Òøœ`!ˆ¡„þ@…Ð8óA”âYÀû gáh…xV¯`‚²F R'_`ÎÜ’ñGTâ ÁâƒÅÙ¯„¬|æýỀºR< ®’LBh‡mUÑn™‚3DZù€úh0I& UÚ-{w\XxKcïR̆q¶?2²Lâ œíô—1«¨ä×øãÕ¢O»¤`<"´¬œW¡å |èa'¸]s1šxìï.šÞVX£ãi9CÓwÿ(è»ãÂû£¢G;»8ö‰U³´sº–^)Ú3nqÂ-LQ«sŠÛS3ƒ/WËeéà% Y%aHä„…C}ˆVeHUÄ-ê–M„™nÕŒ[Ô£q‚¿R·l´æÐãÊ¡„Ÿã6M êx!&¶jŸ$ƒ<“nSOn®ÛÁÑÈÇr ìx'SNê4ȰZô@öúÄvÇÛÉâIB\ •¹ <êà×’Zh¥63Úˆc)Iˆ²~4á–º4‹þX6 ²«iÐnÊøåyRUŽkáW„¼fÖdÕÌä¨Ï)¥víLÈÁÚ'ŒŒ4!óéYA Ç¡£«ÅÃ>ë§a+Ÿ4pÒA/¤/XQõ|Ë´|Ò²G˜óHÊ9\<‘tIJuÌÊ溺EŸ"i ÒÖVÄÐOšÊ„Ê-Ÿó(èZ惘³_!ž.'«UÐÀZ-Ø/VbÕ ºS²wûeGÅ#-8¤;å`4{1K/0kØ.=/d—‚Úˆ™¿’±îQK…ºVÆÝœõ¢éÛˆãü‡âõnééFæl%q¹ž~±]8_Mw#O×Ò§ÝèE0 ój«ðr#wµ’zFLþùzær¼J˜YϼÜÌ¿¶YϼØÈ‚v?0Ò†½Ú*¾Ø(<_'Ääßì”_o—Á úåfá•!ä“{¶»ßì¿\‰] ¢Ï7’¯wr O{¡³~ø¸<í’OWcWk‰‹ë÷Sà?íG»'á³åÈI/ в×ôCxN©öÑ£NèˆêÀ|J<_I>PäsÖ ß C; ºÎç°<ìÇýðÍhýýfàéJò¨I‚œwc‡Í È>ðÏ qÚÁE;„½:a¸€;ÍÀV8îÇŽúуNx¯Ïn368{ax¯M^¬¥6k¤ž®$¶›ÁÝNd»Úæ©ùe¢›wtó®å’»q´2Ö|P[ µË%ïJÙ ž~ÙÝ.X×ÚÞ½At»ÙîEóQMˈÒNÉ–©ÊIËD÷šX•´±–1킈K$Ó-:S!¢Í1sK ‚6>aZréÀ?¥¶Õ ö+ŽJLÝHjek'g,²bX¶³áIÉ“’RD–'ÅÅ0R‰¡ÍŒz¹l*„å@/Ô°k?r3+NÌ+‚ºŒbLUˆª¨WTay‚²bÂsD:8Q·§œÐöË6Ø’@CìÕ)˜¡Ü¨$µËUèjÊ *Ä,à‘nIÔ+ûäù˜¶œ4ô«Î\XÕÊ›·{AؼÑ̨’~©Y9 ÀÓÈX’„Ào³¬¥ðÕºâÄࡆkRKW›þAÝ ´fÁÙ¦dé‚qÐ!ñYùݲ·™·'ƒj¿M@:‘dPUÏÚwúÑ,—­)Ÿ86VÄÉY©Ú6[n(RÚ9C>$«ÄU­¬¡šP_¯aª-ÇU¢©–ÆØ,éXZ­;J1•Ë@sêçT‚n#àÇdÐÆJúÄa'§S‡l\·žúì J!‡†NuˆGÅ ·åKÜ:FÔ)¢ÖÄÑЭÊY¶a¸tÊ(TA•!x‚ËáW/Àý`VÑ­ZœyŒ[µ *dÜ‚1|!®¦GݨÏ"0))ì±kƒvq:¨‰yån#tÈ@àN:­ä Å ìð•œgç»~ø]/ø¶ã{ßñý¼yQ¶~×!x€vþ°ûÛEóÏ;ÙM÷‡†ãcÇ{•Áž¦µoÊ–‹¤úEÁòºl†¤¿å^üi#ò¿¯{įÑ?í$_Ö—%óiÖøªC\Ö\MBrZsï•íc?°ŠÇ-’ ƲJ§ ºA8†‹'õ‚1¨é$ô;(ã¾|ñhöا2Æ7VÙ¬–7¢¤Þgéø£üÙ[œÉ/@ì! ·–êÕ [ErÖCtñ¤š¤3à·Êi*Îc9û¡€þ¹tékþüg¬©¿_šý„uKλ›rî]Â̈BtX-zè·.zM &Å&~„£Ã)ŸÀ¡”²né$C8:™ù{}v,9µsZá^2Bà £tL¼p‹5õ‰”}צ3)&åÜ"Æmý •p—S›œ¹Ï _†1“ZX“=ýùâ$œÌgÀ< ÅÌ{¸|Ê¢š…A8³ŸI—îdóF9éçí(h9’ÏRKf¢i¥xF.š’Kfd¢)T4eÇ„C `ÌÃ[¤D¸4Ø#áM4ŸõChhå€r§Èyå.%=a§œR¯šÐsú¥¸ML9àq(f{|»áZ+ÛbfÖYÃóã~!kã´ i§$„³ßíçÚ1Õ‹íT-ª’,~ß¿“¨ö{Ô÷®õ°ºK5µ4)BT©Ò„$êäCA’ ÈJQU& …‚n¹DÍ–öË Mt,™Tã© $‘zLó‰€ ‹v<à`D v#§6kFrQIÔÇváS„}Þ„MyíìbZ"$zÕ 5Ü%ŽHÈÉwêËÖm7>7¨šöÄÙVød=%R)ª XXI)†•A Š)Ÿ_kØ¡ FÍøJ5šælªÎòqjç¡´Ix%q·(á•£ê!l¦u9Rz8 ·Zî\Pšðòìêq‹bÜ­¥k¹C*îCàm9ÿ¡C?õˆE>£ãŒµ¬•„¥hå-ü­4¾Qô¢Êˆ‰­åéE#9BÖŒê’V~ÒÂOXx€jÀqÎPܸ”sŠí’ñFH7óå´»¤–Uñi:¶M>»œ·>ÛJw’ØJF¿[¶íWí°ªÇ[±FÔêÑòViÀ&u`Ž“t¢„šÑ÷Kž½¢s%¬[ i^õ"›qüy;´Ÿ¶_4Èoj/vŠ—ki¨ˆ|À8$Ζ)n˜y¿_}³]|F}¢œûî þ|=ûl- úåfî†vÞì½Á4@z³S‚¤k xS¶y¹Yüö Hò~¿œrããÿž§+)`@ËA4€ÍËÍô³µ€ ÐÎQ;pÜœõBש—×+‰“^举\M<½~wÍ9ÑSjp}âr- œgƒÈ5Ü>Fͽš¼žbˆB K@ uÈÿ˜hºMêõ®¥‚{Ö‰ž´ÃgÝèI'rРh.0Ï‹\®£^ät‡'‹’æµ\Û@5ûÐq?z:Híì·Cà9¹Ž<^ŽÒƒ~l«Úl·Û¡Z —wv2ö~ÑU aíŒ ìnα^õ¯V‰eêëe=ƒï HžBLŸô+¡Fóâœ(!ȱhg‹ U«ˆç£ê©ˆ2Ò! ÚÅ(T»ú¤Í••„>G]â!rÁ ©àï—],^kÓ>$DÛYÃõc‹$½ÂŒ_ÜHQó6TÊj\ÑHkÚ9m'§ïäõň<’çBrêÃ]'t1¦Î…ùˆ²‘1T“$§UÕdH¨mY` ªŽ•ºkê-½²­_±Ç DîôƒàÔœ¿œÐ% ¿C¹ÇÂM”é:7rHiç- P¤´røZÍÕ+X ÒÎIø¤Í¬uP%ª ¼Óo´P>TSº6™çBÊRL×ÎÙ;G·è*%ðFÆÚ*8r!m>ªï•Üœ½’À«i3x¢uKC.I§h䬃²ý`@¶²ðÛñ^Ñ´Öp­†Ê1e×½¢¹3VšL¨’4ØÎãÍœ CÛµ³@>Àf´ý ´›œzzÀÆJe1¯WL¨„ T$­<``›´¤¡&æE§ÔáR0#¦}­`=Ts¨éu¤ ÷Ãÿ°0ô_ÁοY:íÖ0 ¢q)㾈~ðÀƦ «8ù¯AŒ’)-˜Ç¡Z[í—LH·üoxôßsf?-emÕ`ÜÔñâ À¨7bÇf£žVôPÉ»ã3ÑÝúY³bL/}¬G†4ÂQO/æÍþ^Å{`×ÌêÄÃÒÅ;zdôfI³j4TRö=.ís@ðÀ·À#b~ ›*Á`þ6º/e? ¨ÌãÝR G”ü!ÎÜç€FÌÉq¾±ªÙ6Í’Ç($(0EÖrÇE¬>ã‘”?¢Eé%€Ç¬ç„½˜—(ÅtPhŽÇfÍ=,>áЇ–èøŒT0«CÊ%52/äŒJ€LØÙŒ‡À3ÖÐó!káþüì` ŒÅx›`KES ú=„7¡—³ g¥ˆYÑÆoÓ'îÈ…³7Nk áLBšêöMh)ƒ’®’Lçèås-S‡Î‚8GÂy¬WÐlz–çÈ…c¬™¯œÇ. C±ôˆ7óe–P¼Ø-üJ1íV5¬­†5ƒ¬u»ê=î’zþ[1­f}õ,.ïe,ýŒÝ¯cQœƒóž º[µðãÓ^+Ÿ´So×]oöÒ/–É­”Á‹NáüáfT¿QqŸ÷Éý¦k¿åí¤õví‚^:•ò+·šTÓø¤.„Û)hã­¬B%íì€e fJ·\²TãjH”-P~"h:ˆD<üV_®Ù3!YÀβj§pÕh:$‰ûù1W=É„Åép¥i]iÚ*)m3oì–m¤“ë2BC @ù`Xt @Ô:Éc€™^Á¸×ó>ÝKBS«œT¯6\Ð`ôâ —žî2Ìû­,¨¥5¤s šZ~ ´:¥NlIËŸˆ»¥q—(mÞˆ2ãG"Nž”%½BOKüNV·V1¯”ðfJ•ó ã.NÜ)´*f•¼‡6íl:„âÚ¨!pÊø&làtÂÆºW¹ž0–íýºšÑ’8CÃ}6³ûÓzÞ–¶ð´Œ;y›`#mé…µšù¯#:FÁ)²Šž4#š•¬5mCNÛ‰fÔL}p¥ ¥=Òz\{¶ީٶ˖æk«d[/8ö›aøçrƒÏ,)Ä,—"bGšqSÈÈ:o…¶R–”žWÓ¯šµî´èY jÊ^ šgÛù§Ù“~즧âéZšªÊ©œ,0ÌÛ½2Ï‹õìÛòλëNšßÕßTßì•_nžmdŸoæÞÖÀÿv¿òî°›¯vŠW€Fm7~ÒK¼Ü.Ÿ/¤ßîן­`ó?åÅVéùfñùFáF€^ž®%žo¦Ÿ®So—@_­§@Ÿ¯Ä@Ζ#GŒËÕ8D^­¦Îûñ³^ì¦Ó 6AÀøOù?='íÈÓåäÓA꼃½nœ§Ý(üj8íçÛêR,Ç»‘ã>1 IGMò°Ažv"—ƒ$äCõ}-'vjþƒ Á€:ÔÛÀõ4ÄŸ­\{õ©·„‡@8Í DnÕˆý&¹×‚h*N;îF('ÏÎv#¸þvxµâ]Î;%w/k_){ÖÊÞ5j‚er³X/{×kÞA͵Ùõ®Ä[;Tǹ0vJ 'å‡:NŸ ~+ê¹ Lx0«Iày€C=Muï4³–Õ:Ôø œ,©Š{¥ ­œüµ:RCª¨C±ókqíJÙÕÉ™j ¬“ûPqSðhüÛY};«kPÝJxÀc^0Oœ{ML`Àf¿~…—â åªc³ã»ÙäÁ ×U¼È'K¢kMÏ:&À!aÅv×3¿ æE7ή#ÝHÀ%"=H)a¤6]ÂjO“ò~ÕµR÷¤Á±*I,åC¢.>œÌ2$Õ(—ü>\¸\ Ò.˜xà@YRW£·7³¶bT'PˆéPóóàí‚£_ödÃÚ¸ûЈKq#)¿"FÒÃÕ+Ç4k5Ç~?z¥bßlº¡XKxý‚yP¶îv|ëu°b3­§zÉ 8Ð@`Ä% Ø6˜ͬœFXwœúyðxñŸWä·r è5,K<’ñËcN‘K;ïÇ—²~5QvPY k£øABV>$ùŒ,—vÁ®¢áÒi­p €hĪžÚ)'p€C‹vÞ†1ì:aç»-œˆ…+iDç€y,×oµtÈ´UÈ{A»Øoº—IˬœW Ææ‡ÿ>má¿ÄžÖ}5ÏYÑq˜6<-Ûßµ}ï›Þ75×Ëä_÷²ï[þïºðü¶“ýØ'_Õì?¯F޷ܯªŽïºÄ‹²íÇAðmÃ}Sþí¬úËzdËüëEõçµè»¦ëã øÇíØ‡¾ï²dÙŽ(·ãš“¢m5¦í’Ê j–Ϙ…O¼JºŒ~GÍÒr‹iw€a@ÜÚE@—¥‰ÏG?Ñîðgo! ß ‹AËY”KoÀ Œ9Õ ¯ŽxÕ¥çíXÐ9Î䣎C0ä ]¼.ÝÃåcZñ`„uK¼xˈŽêeOHû’M; À㿞NRQÎ]¿•iRŽå#vlÊ€[4“rþ=!ã £ü‰Ë@W Tî\/ˆöàúèVͬY9¥—jÅÁ:lJ—îÊ8÷$Ô›²[JÁC€ÞFôaßãÌ~%[z¤äÑijá³¹["Æ…±àÑ¿/ÞYšý½R0dR0x¼¸(è@£µÏŠèÑy` 2ç4‰4R†Î;Œ%2gÔ°¼VÔm–ÒàjŽU'ñ§%œ ­Œ¡’Ì„ üpÂ&„?¡Fç%‚ þÒ0°;ÊcöÐgn+¤sÆë‚4Šy92«–Ï´,.ë±5 ¹/U›Ùѯ8óCp¬ÅÙûlÚ#8€âø1ªX( 8Ǭez,“†TÃgÜçÌßÕ Ó¸·e3vÃ’ß!)$L‰€Æsõ’É"‰Åœˆ™Z¯ÇˉbPíÑ2ÌÈxÒ)Ú(»ûiófÅ2²6ŠŽ¸5sqñ蛃ÊFÅ‹2îÚÑ9B»Xð©Âf~Žÿû¯g9B0.T#ÊËèóõX?¤ø°•n”ZÖƒ€ŽeC§ÜŠÉµ’¹e6…*aæCºNÖÖŽê/×…¨æp#±Õšµ–·œÄ¼æEjh†ü“ò‰ò!´•çH¤“7@)rpâ>‰ÏÆv›ry!¦ôšÙ0ºÞñ4òºjFÓ)ãëw.*Ýê{Þœß_––‰Ý1¨Xzóf˳Zs­TùâZÐ})êâ6ÒÚåŠy¯ï[i8¢^¾×Äpãó=êcª„_” ˆ çT@~“böà± ˜yI¯8Mõ) l¯F•õ„&M*´“Gí‹i/§CkQi5ŒŒs|Ñ­[€ÂÊ®§û\36«k‘ %g8bf²œMÒb¢è\êÆäí”Ö‚ŽŒŒzH¹[v¬$2qXtnÁߔċ6~LÇ€¦ÍaÝ»U´÷“¦VÄ|ÖMr^·†#2A»â ãî&Uƒ´n½hÎyWfÉg’(EÓÉ!êQ¥ƒÚíV(ëEiËY'´“·ÅuŒ°rî¤âY ®¡\O›ßì–_íWÞT¯6sG½(ÔÚO×3—«éó~ªõŠx¹‘½Uüv¿öãYçÃQããE÷‡óÎ÷gíÎ:ïê¯wËïjoö*ß7€‚@žmæÀù|+xœór»òj§òþ°úÍníj=ÿl£¨úéZîr5új=wÒá¼ØÊ½t¶ÈÍgéW»ù7{E🠢ÇÐä\ŽRä¬Äž­gá<álÁ¸êÌA.©Î¢$w"7”žó^ü~Ä ’BÅô¢—kég[ùÿžÓ•\Èê¬;n…Î:Qˆ<ëÇ!7ˆßmÚ!¦^Wu#ç«©£~€çºìZ‰‹ ~ή3?h…Žà4`÷nt¿EÂ%ºqî·CÔ›¯Vè >èFw[ÁÍš½â])º·ê¾@Ï5Øz¾–^o@­¬¥_ñô ®R̲‰€ùãnq¿â¨]ú[¤¾ØÁC6!ÐNÄ…¤ŠRLžzÚ ¨F.¤Iùå×ó À9ZM!š»(î’èNζ^óô ¶~ÑZOèJU5NO5®¡ÆCE•…°<ìà‘nüúµT:U­¶ž6äÂJj˜Rò44²F °s!¨X9êúˆ[à6.@Ó#ìâ¯Ô]9€=~+Èg«ã9ù$¸FÅT»èL‡µÅ¤1ÓÕ³ÖJÚpð‹ }Ô#®¦Õ”¡ÕÀQrbna)J l_©»»%{!¢#í"³bÞcä´r6pkijaÓjê?~o¿â͇1¸>í¼c¹âþéWÜ¢®ØEpõ¢n €e1ŠÕRæõ†¯‘’±®GûEóJŶÕò,—¬`W¢*¸ €@`דX;kÜjy¡qW‰ëàT¡|ÐIÇ4¢a·‘éÔ/dHE1¦%Ì,¾¨—ëQ0à⤼2àÂÀ2£Ó1§8é‘ÂfþG»°‡0RIzñh?Î1ËçlÊyµpÔ©cùÌ|Ÿ…° Õâ1»Žé· l:†ÃÈ"lü [â2slZFÀ&ΞÑIg6q1jÌ…õF9 8Ǧ]„¤˜WA:½Œúb¡æS¾Z‰ŸU<ç5÷EÅ}V´¾núä/ë±7uׇŽÿ¯{¹¿îÝL¿¬yzNÁÛŽ+ªZ ȪV^Gk»ø›!Å‹¦ûà ´Uþ°Oü7ËÿÏyýÃrð/Ù¿¤x^·Ý§9ãû•ÐÛA¨EHªnqHC ¨h*æ]wXÁøÆ*Ñ ÇÜ&ð PŠSsGs·Æ}«œAÃ{ÜBõÛ°ñ¦¿âÏPü8†A<0ñ8"o Šh‘1&Až¼Ù¯hÿ,|!b|É™û„3÷©’j.¨>¢n¾_ˆ]Oª@˜à ØØ™Ôk†›îj¦Jt߬‚yåÞ4J¥fÓÎaȈRðÈH xŸ@¹÷ö]ñâíëqå³ð€ÀÐJ†Á@‚T%ÿ¡‚O¡ŽF4†r† ª•².Œ}£}5?ú;›fîfvJ—žr(¢^ ®dí˜ÔìWmP2u †Ë$’KfŒZ¶Ç‚ºMR“†ëÄ“†ldBØàÇÐE)â¹p¨TÑtÀ£V¡tÎâÐŽ€óG¯aYM›Ih1òM®AËV¢4#¶„ˆ&Eìq'”¡•1!7ñÒ€ d¨ÍDY0¾^Á†ƒ èL ¥!Üaƒ’îŸzA)‚£é4—‰ò¢£VˆÈ¤aºM|ÆâÏ}ÝNÛ²~¥’3ä7,e EÄÊ/ø•yj=Q±î;³/63kÛÕZòO¯V ã=¬eRm’Lx4 Ô¼4zVÊd Ù//•Ê®˜¨Ç”/÷Ò‡M÷»Ù¿œ7ÚAuÑ‹VCÀ’ ñù‰©\º‡²dÓ‡dµB ´“25 ŽxTÓmùÛ bc%²Þuê®nÍaÅfŽ%§–!¥Õ´&u ú՚ͬš†R«_q^ww³Iàö9¹„™é³2|zeËjÙ&%I¿h@¼!¬‹QŸp{™h—q]3¥à?Ö£sŒg’3&N7kZ.X«U-¦)¨Î®jD±Uwôr:ŸnÚ¡Éx9µ(RýÆ9£ä OÐÁ¸ùÜJ>Š)'4’q£t:bexœÒnTÛ)Ê^^+Š–H©[;ãÑ̼âvHÕ «ãØÂvߊö³Öµ¸°dÐX>lý”)ëû1~ÌŠ¦Ý*Ò*òᜬ@[²é¨FfÝ,[ƒ¦C9¯â<Ñ!4„?áv ˜rÁ„1óad·S8µBeÉÑ"•7²™1o¦M'Uoۯꄵ/·‹×½ÿѹ1ˆŸ’ g½øÓÕôóÜ‹MŠvÞíVÞïUßîU^o—¾;m<ï~¼èýò|å§§}Øýá¤ù‡§Ë?^ö@Þ5@S1çÝÎzßPú§ËßµAÞìÕÞî7@Àx¹]¦:y6K×FöåNþÅv–ú~x5þl3 öóÍ ÐΛýÅ<ÿÏ׳O©O“Ïײ `É€~¶š°¡¨f9 ÐNÐ×sc\À/XZI=]ÏîÔýÀ!7Èwz9~¶šzº™½ZË\­¤!ˆ?j‡wª¾ÃVpå¤":ìEöZ$ìµÛ¢zoÎozz1¸€—ƒ\ç+)Ø‘šC’ú&*{Ò‰ìÕü-x;êÛžÞÍÛ®ÀÞõÛ4°·¾åœ}§á Ú¬zV Îݦÿ| Î'nÜ# š¹¤U&”â3pªqC;cjgM•˜˜?EH×êD¿à*Út@l“kºEG5…“!ØqBVK™2A%0O¯äÌ’ª‚m ‰wJý8ÏoâÂt3eZ.:ë C#elgÍÕVލëI]5mwü¢¢œÀª)= K»`É’ò8!)^/n¾B­®z¼‘Å©Ž¿Ø öZm¸7;þóÌfÛ·Ñ&~¶{óít6D½ƒ|òM¯ê-$ñ^(¥Lå” ø'×s–ÍN ]´åª˜WB:xPtD]‚J\»ÞòÀŽ…VŠª)sÜKuòÄ ¨W¶SãÈ‚R0€ñ’>e: Ž»‘BHµV#ú%G%®ïä­ƒ²›š½0mº^wŒíÕ/9+q,TVúÕªcµjßl¸)l§Cl6ݽ‚ àôv›€Ôõºs­æØëù;ÔwŒ&«š†IÆB\m·°òá‚“vjP•Y9-wˆKl×ÎÃ&P @ŽÏÈž)š|PåÒ.8Ôt\:i‘ϸ1&Ø`Ø”sNÍ|ÐÌGÙCp8?ì¼Å ¹ß*T Fv ŠY 2ᇠCJ—™­DF-C#ž0«LÊy:T¨Ìã2p~¬&® ƒßo9tl”6ôI/fx9ˆAÑt\r\V=Ïëî÷}ò»>ùóZì"‹¿¬8~ÛJýóYãû~¸ 7Ì|fe-›øD5ó‰añ+ãKã Ýüçzæ—aÕ¤Cø ŽÍ¥ŒóØüç/WÈ¿½ü°üÇ“ÂF@Ô´.þq/õ¼î\%Ѻ“W´qNªží‚u-mvÈ&L‚Qoª*ÕÒÆ¥zcèwM²Y Ž«€Vq†|€a{ø³·¹Ó_A˜A2<ÕÂõû #2 {A¤*VöÄù°Æ~/˜ûš‚%á¨[7oVOÐ'rþ=@L: ›Ó|šDüÖEh)C­¤E†lØ4n#t²à–9‡~†t,ö ØÙ€I5Îëà^6Šr¾AØ÷ ·cTÂ!µh´E= IK³ŸŠ˜·Œò 9ï„É8÷LÊI£6¹´âq)ûÑüè'‚ù;ìé/æ†?Qò©© Å‹w è„MK3Ê'õRºÇ­C>bN !—ÒeL)whG-™³ëùa¯JƒÒ¬:n³ð;Ù¼Ý ´˜œiÕñAkÑ eX0lTlÊÐóÁI8Q™ñÆ€m¬8_-ŸÇu@µâ¦çgD&žF%ÓÖFÁT¡óæˆ=zý¾lÀæ:+¾hiÌ d›1nØ«#Ýj³–¯Bh(ƨG|vqÔ«t92k€;S>¯’Ló@; ¿ÆnX1GÒdÜ:÷꨹ێØÕó öOφ[BÁzØJšJ¤Ú®˜ãO~þ|+÷íQ}-oß­ûó~Ô«¥ût vÅ´KM X.=ã…j]ç×3{µ²]wJA˜g³hÝΊöc(?c†ÎÙ­¯v²¸èqĶ+gpÙ´˜ùÀmà¼;íô³(„—;¤ÍÆóz¿Oº6ˆn­'c!¥ÓÌ®æMA'åÝ'œb\EXr$Ò/[óUØ%*Eµ­Œ)éEò!ežTÜhh‹AñèÐÏ…\œ|uéçZY¬ìv-Å(ßkœ¨Ä‘bš`ìAÕ¼¿JÖ2Ú|ÍH!®h—ðjF]L*JIU¯bM‘D!]| óZ™ù¸rаw+¦F^ßÈí<þ³!š hZiÓÍÂë^ŒwòÃÖ%¸`@FÊaE1„:Õ“у")ÞªYÛ)e;­ [9~óaY²hgþ}› 5JîˆSµqHë~ÕðLÆÐô‹Ê^~‰Dâ.ž[= ÀSñ"!u7R÷ [)ÓjL_óÊ NQÅ'+¸D?z1HìV‚1+¢ã;•̉ÁÖ‰Æfv+­ËûÅå|¥dsi烾I1g¤zgl¡R6cÕ³‹1CÔ‰lשµA—Æ¢}%nhù«qÃi£ìF·*þ”ͪƒN¨ƒzÓ£`àùzæíné»ýê·×Ï¿?¨½Û)^­¦ÀùÓE÷ç§Ë¿\õÿôbýÇËî·ÇÍÝ__¬üájðñ²ûæ úá´ùî¨þö°öíQëýaóí~ýW+¿¾XÿxÞÿñ¢ú·—›¿½ÚçOߟ÷@>^ö~zÚûxÑzT{½W|wXýá¼õýYëÝaåÅPPîõ^éíAôó­ ÜÕ¯v‹Ïש^Ë•E&k™›îð¼Ú*RÎÕ̳õìY?È‘×\ tÚ‰^\Gžù€s+§Ë‰“~ ôùjêb- út%yF½KœµÂgÀ-ðvÍÔì·Bû0ÄŸêœ,Ç÷»aЗ×ç\ñvÜ‹u£{Mòœºª9Èj³è9@ªPà‡ƒ£@¶[ub·8êSÃåöZA œƒnôjѹUóž¯&O–cƒ¼5æà·ÓÆfÒñÂjÌûÕ!3¿_tv²–JL›ðˆŠÔ¬€ªFÒ”V©í]¹0– (»%W%eòÙRvŠx  †jw»¬' q$ãW¤eÌ…úM|¸snî7¿‰ÓÉR3UcºbX] kÒ~¤VçIe.¬¬$±nÉÆÍŠ™À61¯8ì¤2›bL @Rˆª÷W"å˜0©“7ÃÞHÀØhz[YÓJÕ•ò!­ ¾Û#Á^.6xÖ¡ÝTt–’¦õ6YJâù˜~¥á¯fLƒ±Õ%sa5éàãç2.ÄýH·híWìýš=ì¤Iåz‹Üh‡WÁµ&Ù*8ÖÛÁJÒóIZs·ä„kRŒ™r¤Î‹sŠ1Ý^?Ò/à  I¿œZ<4nèìí‚®L1¦…Ÿ'J\·¿ÞíÖj®BH²-µ2Æ<‰‚”cê~ÑÚÎûEK#¥[«9»yH9ª6£Sd"`á Ç"í"qɪ]°cL•hĤœEyPý㘠£曥ޙv5݉- *îZÒ»e“n=Ó…-B{Ç­cFŽßÄ3 Só¾hþø«È¤¤ÚżpåQhpéåS„•kÖ@K™MR/Å:tÚ¢™7(h F9Í¢acÆZÚjU/è¤Ó!'B:$n#6{PîcøÓ‚ë=ÜäëaÖz^´_”ì¯Þu竺ë(‰½k?­Ç¾_íÄ´^ÁW4Œ-ÞBé¿·ˆ† ^AD3v¶_=©_º£ZøÂ)!±Ù~L-ûÔ…Œ\ Èãš}+©nº¸-7ïªå +'=âa—hDÇ¼Û *»qÃfÑEØN% ZåT]6ý…ˆ~ª9óPŠUN£ºq&¿Xû½xþPš;Âýè…4 Ç ´lΦ\0ËçÔ¼'Zá˜I:ƒ‰&¨¥—h_#ŒoDów¡D5H'Õ¼Ç §’ÔzLtj]Hà#ݨÔ‰ûDztWŒ£Üo裟‚öš^3#ìâømŒ¸ï2ιqx€‘ØÓŸ2'?ª±c4­dĪ™zà‘°îꤣ&å”›§Ë0›"æm €@#þüûŽßº1 >µ‹lé‘liHÊ~° ¼&ÊÂ$²¥‚ù¯{0dœ=ý™Ï"vê¹N/æÑdCx̃E\j;Æ÷àÀµhƪ]‚£bA'§g£æGí± z3êÇ|vÔ ZÄä î˜FFL)%³¸† N\ÃÒ)f‘^½¨@fÍz.ޱ¯ gS³05[­`Ê$³%S«a£ õõ¬;oR‹Îíð‡î„Ý(Ð+!OðÀ!€vÞ8xË(pè¹B³ë8vŒkV36™EÃ&‹yÔÀ9`l@Clj8Ñþã³vÖ)Z¸g@fìª!íÊzdEi^Œ]›â6$áþ÷?>+øÔÆÈÁ‘Ñ­º—ÄÙý«¤S´^rýò´KLYЂ>l–ž|–÷"ïJûU÷J\sTqìä,Y+×N~<­ÿûÏ'ÐÞ©”ì!#2£M¬Òínj«ˆz”Ù¤!R¸ü  ÒÕ~ô`+·¾-g,Õ4%ÔTíNhîɃVŽM=÷I[Y(%L´¥•À‹e=ª+ÙÚÊ›CNh‰Üzz!¤èåLù²Ö°ÔóŠx€í³ÁE …œÌ “ ·œS7ÓÌê¢.^=­[­;û%ªä²(Ss¹'56ÝœI=ñŠrQU·bë–Í…z²ysVjçq‹zF-õY¸Q'?UãÔ¢ŠÐ:+ÅôP<&Ô' ©€"á“A»¯žÒeýH3¥íe Ý@-†yt ƒtܬšqè¡•4£äùO—ã%×~ÖrV´­¨ÆK”ù tB3ãÓÌ” d9®/ºÄU?º]vöSxÉ/o' ͈.ná{ä´‹åøûýòQ‡,ùnÕ¼ J½w¡¸tGg³¤¶ž¶@]æ4²õÊ94uÌOá„¢Õ*Ê…uI¼ä×7C;9çfÒº²,µË$vRöW<ÀW+ s7áŒZå)§z9íy¾^z³„S}¾’º^-+õv=õ~;ûv+ór-ñf#õa¯ø—+ÿúñðÿýÓÕÿúõüß~:›áß9ÿ·ŸOþן¯þ÷_¯þå'|³úóË•?½[ÿéÅàõaå‡Ëîw§0u°ÿøjí·7ë ¿?o¿?©{ÖüxÕûéùò÷—7'õ·ÇµŸöÞUߟ4!øãÓ>DþåýÎÏÏ€O?=[þá¢ûz¿üæ°öæ°úr·ôr¯üÝiûåNéõ^åýQŒ§ëÙ›WiàÍç[…ç)¯í<¤¾c-ûl5{Ò‰·"@;çT_ÕlsFuÎPBõü,'p€U(ŒYÏ–ì^w]¬gÀ6È^;tÙKª )yØŽî5Bû-@—Äé u ÷5s í¨zº’¼Äÿc¢µÜNÜ©®6r`Ÿ¯¤6+D;aª†µ›U/0ÏéJüÝIõõai«A¬lƒ¼¥“Ò7âØÝŒ©7,çlÕ(¶R¢–» àì|@‘õËSZi"I!j`pãÜbÔiR ~p6~) àa-G5õ¤¾QW"êj\Ÿð©m;îSµrŽnÞI-i×/ç­½,ÞLêšic=¥ÏG5¥¸¾”0$üòJv†‰8RPï­ÂJ0ª)=Ø·°'ê©éˆ¥ƒŠ³~ýTæCòj\ÛHé7›ÀƒƒåÐzݵR±wóævTX©:À=¾Û×ÈM¹©m¼²åšww«g©¹ËI}&¤ÌFÕÝŠ}³XizªCÈ#ŒâAÃÛ­¸*i¼[öÔ²VÐË5èõN¨UpB)Ô¯úK +”W•¤­–¶eI]¯ìkça—4áS„\H-eÙYŽn¶É^Å dZ©{:EÛ àÞ¬vš¡fÊs"q—±šÄs¤²›·ÁõL²„GœÈ— ¶NÊ”÷+á±Us†£vÄ©ejD㸂pˆSa̪g:Llƒf&”úœL1fÕͺñÅlHíÁÙ^|©dz¤Æ‹sèŒM³ —N;°E³’´5qÔ*Št›êb•CÇ1¿Á•³.#Ók†²qÉŠÍáÊ)‹z.w…]@XøÑë÷N/À¢^EÀI FóÙ/b×3oÂâ^”0qqùœE¹À£ÝãŽyV ^–‰÷ýøk©ó¼íªì–Í}¡ç>2S+ì=7h\m”â6YÌ&!Í܈×J:)œ4±¢'͸¾×Ç-Ü‘u>ˆíÔ¼ÈÌæí^±T\´‰ïvÒW½à~ÅîE'›þïŽk¯¶K͘ ]|h„‹ £ë$pbì8¡‹t©¸Ž HíÖ%§§WÓ §83v«þíåd¯ê?\Ëoä6;áÍÙHã¤ï·r¡©X‰h»óZѹQ¡–Y„²7€’GâÂr5¢<$Ù¹VÅXÀ¾PÉ(Z%]-§®¤•q7äb—“ÊJB]Mj!¬™1ä€süH/gÙn›ñ~É´±!+§žAX9²ít+ 16õ„S3Ù+âoNÊ›®šbÍ~9 ŠfhšE]˜ÈyÑE|6èG¼ÔGŒ¥¸®™Á!,”ÅìK¤¾™Ðf¼b¯áÑ3#NaÜÀï š– ‚á&©Ú/Ú¶ØÀ'Y÷!»qMƶT"e[eÇ ƒ2¦£Và¨|¾‘Ù(»y{-ªß¬ù6*¾NÒš´!¸é 8D~y¾üñ²³Kt‚‰ˆM0IwÃNE5íÈDô¸–)GÆõZºÇ)Ô©iJd¦[ ;³t6 ]Ú¯öóÞ½ŒûY3²Á×cøU;rPtõ#Úf@[òã9Â\ Ú „~9éºè&¿Ý­^õâÏz‘«Nèy/òŠZ²œ6>^uþü~ó_þx ò÷þí—ÓýÃñüËOÇûéè_~>þ·_OÿçŸ/ÿöóñû¸÷O÷ ìOßnþñÝúoï·þò~÷×W뿼\ûñjùÍAíÕ^åõAõÙváû‹îOWƒ§í·N=wÇ °?^ö€Ž¨ÔgpÂæž¯@$°ì~“èïÎ:ßwÞ7_mßîV¾?n½ß«½Û«~wÔ|»Sy¹QxµY|y=|þõ^,} B Š|ÖÒWÛà³µ4ÐËV=œsЋ‚ ä³× ÕõãÀ?ÇÝÈQ'~Љt¢û(¤‚<ÛÎ]¬¥®62‡-’ßœ¶Èã&¹] œöSçƒÌÅjö| §Ë‰½&éÏzÔóýŒõåNñbíÿãé<¼7²…ÿß½w6Úk{‚'ötN44¡„%B‘sΚÎ9LOOÎÙ{×›ƒwß¾ïÒóÎwÎ=u®ªJ •ªnýn©T•X/ó½,½œ¥Š>¤Ç»iHÚ£ 8f‹´eÀ’tX.u?ï\.ñÕ¸£Æã$àÒýºÉÅ„„¶ |5MǼèݲs9f«ÆqžNÆQ[ ~$åEBn$°¥üX„Gj ª·'xÍrž°_.2@#Ã=ÖcÖjš„X®{;%L˜W'$=x(ía+O7²äZÓ»Ú’ÂÓ¡žqôKÜFûQã·â~7°=Ü\Ãíôá^n£&ìu$€:¸˜vš\´(°m¸ß¾\ZgFB¦[âëYf½h¹~•ïVù&äl «]©œu$BưOÛ®pëý`!ió:#RH8AKP@Ò[»,–ÓlØ‹ÆýÖlŒ.$ØZš_©…;Eqµnd]»”`õŒ3êEb¢1²ä"ÖZ†ZkI+ ¡SäÖÁ”UûÝ\U!hMz ÑXˆà9hMYºc¡T»YfPäv›Òrž«ø±„SëµÌz°9Ú0îÂeŒeÖfœð0J‰_r1 ”}Jòª)bœ´ÝPÎÿÒ <—‚G“tÀÿ¦ƒ˜ÄªÜļ°„×[µ×Ýv9‡ÏÛô£ã$“òª—RÓ]ZL{ÅCÊuxÇŒË>14wÄ$ï˜ ¸T@;¤yþ—0NTÓ®ZŽO0ŸKðè21k6Љ¬\r.‚Ä¡¸´¼]ÆÛ䪩OäW~q»9-xž´Âo–ÇiêV޾[â¾ßÏþùníO÷êÇe&€^á4ç’ìlÁ§‘ˆI¯m"ÈGÅqÍy‰œò±©>Mò‹Æ™_b²ßÚä±Ú Q¸6ÝE»ì·@>èÂo Í9Íø+®ÿ®ºH#£Èܹ™Kÿå§ÔЋ©'?RŽÿ†@FIã(‹£êK„öºUuÅ¡ª Pªð°SSÍ_ù/Å_ª'>š¼ð_àòCªQvS_ÃÕ7¬KcfåUÍÌ9åÄGš³]S‘…Ï(ó8†Ç1 u˜‡Ö¡¼ȉû”é Zd¦ÜÄhܧ¶>'ÍWƒn¹@΄y%°}`è­›*I"™ǨüÄMº7"Þ»N[®¿žÿ­YyѦ¿ŽÈÏÀ$‹ç ŠOäãÿ­“}©3Ví%Ç?åGìÈ`ž³ý©ÏÛ@âëÀ?ÁŽ\žQÏüN9õ‘|ü×ö{5*à—o¨§?6È/˜”—à0ÀiÝAÂ<ñ0oqÙT@;¸vŠ2Í' ÇLÐY§u>îC“A‚²Êü¼‘Â|.DÅ*™›øØjœÂÎþqQjˆ'€Žh m_Äͳ¥8„´-¢È” “€sôS¾ÈÒf‘/©ÇQã,¢"fá’^y T7A ó6ã Õ7x0dÈ„µ+ý.£ xÆ®tbríÜEÍìD~E=}Bãâ5•±Øk‘†iˆ¡Ñy/¥æ0ÙóÑÙ¸`fÑ2.ØäPUlšR?N(¯.^û•iö‚aú‹L€I,…PÑ6ó!¥€Ù,û„ý,ÃGk!Ë^Ý׈X-³×CØãíüV‘;ißœ@è?ªóŒò|7f»=ˆ¿=é”$L?ý kš²:§E®“]"ó¼C ˜ƒ~=ïTnµàTyX%Gȼ1!YItÊi…ençÝkM+Ö†(Æ,IßRBP¯W\7—C«E¶™#l¾¹Ôñ9e½Š³_ãV®• ws=rº—Øß4ж\)¥Ð\ÉÇMƒß-³qŸ¶Çb eqY'‚.åÓ¾¾]TØá~ÍÄÇÊ¡iWRöíž?Â/æ‚zˆ±›kÑo_m¬Ç}¬¬D:B'mó½@”WÛ´€î#ã‹M¸0øYM|ؘ)ëUy°k”ö<]—ˆ)‰ž‰ºá†ß§dˆ(§àŒW;qì¨îºÛò<é‹/–}{Y[?Š ÒÔzÞYtÕzÒw"e!ÁiCÔ":žÒÞ —F8㣬s‚MrjI¶‘â²~[=Íw ~ yÒ…‰µJB%Ã¥¢6–˜3j/…}F£ú’Qù¹Û6'ÖÃŽ[D;@¤õVš¯ h/d;¨øÖRÌfŽ[ËòIKp¶‹:us~LÙsÖò÷zÉýøÃAüÉZêùfæÙÆp„çÑZâÉfæN?z»9íFn÷¢Bß=\µo-u{5q¼=ì…n mç^œÔ¿¼ßûêáòO7ÿtóϯ÷ÿùõÉßßÝüÓëý¿}yüŸîýïïüóÛÛòów·ÿõÃÝ|{ä_?Üûç·wþöÕ Èß|ýp °çݽÁÛ;} š'Uàœ!ÒÜ[~qÜ|yÒ‚Cˆ|q³õúVBÈgAfˆs» ‘öʶ‹OjOëvJ_Üê~÷`í÷O¶ßß¼»Õ{wÚ¾W½·’y}Ôz}Øü /öë@AÄ6óû­Ðð•SgÈ6w6²w7sÝ(€ „Çý8Èþðëªà^k8Գݴc‡gÌsÔƒT(“äñ qs¼»™=¾óŠ´‚§ý(ÏI;tXÔÃÄN#2È{z)n½$Þ]ÏþôbïÕQýp¸öãðs¹“^äV?:\q-‘´­˜µ´÷³T=Š%\Ê‚ßØŒÛÁeÈIÆ´)ÑJŒ¨'ÈbÈ`TÃåsIy2`IøÐJ’nå]>Ztª*pJSPZ{Yªdz>mÜ£ñ±jžLЖÛ¶Z¡­–2ôóÌj™ëçéAŽÞÈá*)2µ§C–bÜ0“ £å„m¹ÊáTSÄY»Æ>OˆWÕ³ÃÉ<ň¥Çk -{7φ`fóSòrkeèí¶´Ù”z0%÷ ìj¦†¸U‰ku¿,ÔRL¿ìidÙBÌ¶Ñ nöB+!-_=Gwëžµn ²ƒ¬õ@;¥4‘™!´R$÷’›’<ú\Œl<¥”3&r1*&aõ,ß-ùZyÂRœŠxŒ¹° ”„dñZø¯|oæÙõ¶¿‘c–«ž•’§'Û)¦r=E&|H6dÊG-pËpwõ”m§å»¹½·>왥«QF"àóŒqXÂa?÷JH@\”œ´MqŒLàtªOlèUãÒ9%Ë KÕp©`¼´ÜǪ’~T EZé²-¦)ðÓ9ú‚YÀž§XT0Ж‰°Gµ5ÇÙ&xÇON‹Œ<Äk€ÜœøÐÁ§ÐétÐà¢sÉ¢±G#^}2` KQA ™‡k5ǰ¥k”iÊ$¿L¨F â~нU6üwÊüë•èó~ðI×÷÷ÇÇ˾ŠG! ½Æ+z•Ñ^p£#^û¤Qþ1k_ò†MuŽÒ_âÑŸm"BͲúKÈÄcó¿Îñ‹Ÿ6d›pê.íÈi¹Bi¯ðØkÇ”Wä#¿W˜Ç¦QNüƪ½fÕ^%M7{|¤‚6L ŠK Fx TAÇÕW9tÚªº:qþ¿átLyY?û)ª¼²4õ²pÙ¢1+¯À­9Œãð;ÀNA—²’Ä£‚*Ä+oªik€›KúÕ5)P¹ˆA ¦xb2"(¡û€R=›oü§uŠ+À)=Ži1Â;FXëU/3¡•ýʆ\‚Näl³×KÀTõ%Tõ¹MÓ^¤a±1øÓ¥¹ßP摳Ãq pu [ºÀI>aG®ž } À#û•Nv׎Øt#@8ÀlÆÅÏQ50Ò˜øñsÚ˜Mø° ËáѹmjÂ0G™eBäŒ"­ålrÈVN±!u`óF«U\áHU¬·c7Ïð áG;nZƒ$åÜÅÌy8ä¦ΞHY9‚iÆSuÃÇè€v‡f86ežc,2 7žP¥6Ö2v#AN/R*à.§‡6æ²Ì¹±y¨0 2É™f‹,Æê×Jþ½VÔMî´üGýHÒ­q£cªÑ_2p<ƒÄ\/EÝßHo”Üsç~Áé¯ß]Mv¢x'h|³üf? ãÓõ€ùþjêÛ« Uþš·ÊR"s£¦pãJ‘ÑE$c$hr1<šz‰´Â¹јˆ\‹Í1ØLÌk p‘Vl÷ƒ'Ûɵ†»žÆ·šžÓµh?Oe%m)>&¹e¢s> (yj:,(¡ÖURørÓULc…$Z΀fÎFM»½<ÝÍf‚¦L…vA›Æ¬2ȩÜr»ãëä§ÛéRÒÆÙ§ÓÃÑis-K¬5<‡+Áf ‹òŠ´_†úö^aw9\£]o7c;]­×Ük5wÒ§ÍŒCˆÂǃ΅zßm‹G}ÿfÍÙL  ·,ÊÍ¥½ÊZÒÒ+Ñ­‚£·dBH!bì÷7¢§}éáZà~_¸ßq”Èe0õ1G=„‡r(ÃÓ•ôFY ‹å€­¥xóLÚm‚‡%Ê ¥çhR\²¨¯ÉÇ>ÒÍ_µßiÔɆ¨rÂv›[_#ÏÇDÔçT[´×ŒšÏo”">Än¼ÁX¦Er±!Zq§dYˆ“Ku¿­*bki÷nE¤Ùn‚J:µÁžöRiž1u’3ïVÃo:Ï·+wû‰GƒäÓµìóì“Ì£ÕäƒÕá^ O·òO6²>ßî¢uo|´‘}uT{~P¹¿•½ h´αÙ6È Ãµô•Ô“íÒ»Û½¯ï^Ÿ´¾8í}÷dã¯þðzï/_ÿkø.ì!0Ï¿¾»÷ïîÿõÝÉ_¾¼ áPùâäCÌŸÞýôjÿ»§[?½Üû€C?<߆È÷ÖA þÛ'ï­ýþå.$A `D‚òò¤ýx¿úäðì–ŸÔßÞì|qÒ}yÐxyPsÜ~uØ|ºSy¾W{¾[…È/N†©oŽZ ¿:j=Ü.ÝÝÈ,'{±›ýÄ)ÜÎZöözöÉ“Aò¨?Žä„!.à9hÅö[ ÇŽûÉ›ƒôÑrâ ;êG‡Ëïô¢'½¸ 'ðí³-*Ú ¦qÄXm„ÖĘ%Á2g47»±ŸlüõÍÁOO6ï R€FÐèŽþ2¿–wöÓŽŒ ÍzµYQãó’±·÷ \=Ad%cF4TcD¿È·³Î´ßœØÒA¬_«)¦œ êi&Äiâ‚®“s¶²T+M®ÃT݃ÛH@gM¤˜@i"‚±[òÂ_o¤·ÛÁN–®¥œehd©Zš*%‰RÒ’ ¥Õ· l9i+Ä€¨bÌ:¨ gÙpØ+»ÖZâjÃÛ*0¡±¶2ÌrÉÓÍsµYŠX%JqiŠa|­ê.FT×ëR+M§}¦Œ]oø;y÷ êë=™ %Ä*)*µ•RdÌgH‡Ñ˜„”³T·.¤"€7ÔÎZ¬^tæ¶Zi•Ý­ ß­‰Íß®xã‹›\L…l­’Ø,Šå´K`4Ù´³R· bÊoMmÕ4›‹Øu©œ¤Šq˜€§Uà2!´·A97Žf’ÌÍÕ$Ñ/qpSÕ”m¥îi¨ÊÙ©½³^sí¶D(X·y „3 w®¤c”y’¶L¹)E½èv:dnv‘£e5‡¯ œ,›°Äý†¨IúÍ^ZÁ;d]¥p2èàRaÓ¡êkn»à°ÓÞæ%m™ŒûŒ¹tƒž—¸Ÿú8eسTŒ;üœ†4A'®C“,~7‚!7Óx2`NMÃ2LI¿Qb”\':å¶É Ý„×*;©O«¾—«éoªïJ?Þª¿?(<êIÛ)œ_ú”ýRD¯Æ¨i·é ¡ù,î–·Ë´Ž 3æën˨hŸòâc€4@;^l”Rfœþ…`¼š÷(3.…°²´  PsAj5ŽÚT—iãøüÕ_(nü˜‡Ãf Ý ‹æ ðô~Vi”Gæ>3Ê.pè kš²i®È®ýB;ô¾§€ypõ5§yZ$Ãy;ÆI™´éÆíú ëÒ DvQ;ÿ™Nö™ff87Õ|.sqQë&¦D8GWJZ’þ¥rMú5A·ÌÏÉxÐŽ*„'¢¹•zæÒ¸N¡c.Û´Ó:æ´]£,ŸSè%G¿µê.!òOX€7l .‰Å&‡ðI­ì#‹æb̫剓òœýìó=ãl“6äŠQùÀa¸ÆXÆœÖ Î6M£“i¤qÓ\ÓËÎ[µ€RW€u(Ó0ɦƒ  ðRšáLL#1T5Æ rW0xÊ„ÏRÍpzÉç6„}˜|æ3À›h!­2žY²¡ZjÌÚ1“ N“PŠ:‰-pµÈÝ”ÖjŸz‚Ìb¦yÔ0§×L¨7–”cÈÒ´™· B2Ú®¤áq…›Z²¦Õ²K9À68Œn(bd“Ÿ.L}fP߀l"£õj€£âšNv ‘_‰y±€Ót£æ¶+ýN$å'$F_Š3IŸµ™uGx#à«H*ó!"á1'<¦J”ÞjFÖ+Ã^j¿߬ïmVîn=ÖévšüÛWǃaË%ºÚe]Î9ö¹[˱Ç;ùºˆË>àÙ,ñ«ú^/üÓýîït_ìdË‚ŽG®ƒ¡þŸoîtS¬òƯŒ¶t„9ƒ_tÛ”I?ž [“Q«›Q0Ä\­àÞ¤úõP«äŽ“l>ç’‡Z̆ñ°(ùF;Ð-³;=_+ëH‰Kí¬£™&9GTÔäSX­Hyœó6’K`¥ž Å4žKY{«âÌ'ðDÐXã–¥Áã ºÁ?Ur¶ÙRœÌ­NëlÄ­mçHªG7+qIj/¥Âh&lÉE-Çëñ½¾´ÓöôŠTÀ)Kø€"à®õš3åUö TBÐ4³Žj/UŸcБ„¨éä©¶w¯ã]«°ƒ‚£—Å›)K#aiç‰AÕÙ«€U7‡=‹1QÝÎÙ—KŽ^ßk8½¦Ï™Å_Ý !lº*aëE¡%*!ûjYl$è€CYòãõÍhÆ ¢-ï³q¦9Æ8ã0Ì81¹›PƒxH™EÕãã¼YÍAu;%ÙÁô‘ÐÀ%XÅ„dLŒ¢,ä…Nü;ÑÙ¸ËX0I·ÙƒÊNd%/nUÐoT}aZ#â`mÌcÑœ€¯f½÷¹ÓNüÁ õx=ûb3òt#ú£õÌõôãµ³p#ûb§Øso@(õt·ôâ òpk8¯øÙ~ùÅaåáNþÁVîé^åáFá´Ÿ¼»œ¢xuØ~¼]z°^x¼S~´U~¸SzqØzw囇›@&ß<ÚüþÙ6PÍŸÞüéí៿8üË—Ççïoýüíí~sû_ßÝù÷÷÷ÎâþóãHúë»ã!œy uø o ÈO¯ö~|± òÓ«Ý?¼Ú{s³ýæ¤óê¨ù|¿þt¯út·!\ÏÝá§ñéûëy8|qP²SHÈóþÞêðÕØNåîzn«,nT†»“ï4‡­ða'zÜ÷âG÷Waû‰ýVt¿ 9{¥ÝkGwšÁ­úp½ÁýNøl‰žèq'|Øðß.øœxwõÑfy¯¼ÙI<Ý©·c[%ñþZ®îÇÖ²ÜNE¬ø-%Ñ8È0½Ù‰Û÷›¾­ ߌãõ¨5'êô €§“¦%wÊ‹$<Úvš^­Š½‚p¢Æó¼™s®6+U±ž¢KQ¼')G?ïl&‰r[.:ûyfPO5akd¹.¡:áCk z¹âÙhHíSÎðÁ+ [%iod™NÉÝ« ýº˜ c@8ë-_=C‚ÇQI…(¶Ú›yf½-mvümi¥.ä¸ȹ ø‘eÛY\LZBKQ[Ô­—˜Å|‡+Ü_Nìô"vi$J™ b;Ýp¿"ÔRtR2‰Ì"@TD€6{áFÞ¹Ò,·¤Õn°ßôEýÆ´º7D›ew»ê©ä˜J–ÉFíý†Ô«KŸ‰´Ì„Es£àiW|Í’7$ÚEïN?™ »¾C.l¯¦Ø|Ô^J©ZN’õ,“ a…Ä“ËEn¿¼µ–è—œÕÄp,·œÀ›ª‘'¡ágú¯È‘ZíeÝ,±ZtÈ9Ö0t*¼Žy7"s‘ aìs.JŽ®ã¦ 1“‡²Í’³àÄ¡ÿU°V@£É°G€k‰4OPè$@‹Ó:gCÆÝ„ð üG³ê a„C™Ã4 ¥„=â] ¸• ‰ŠÚ¸ñ²°GïÄç0íu¿kèãG†þÑe™–œš”OØp«S~c ª“[IÑìgÔÙEóü…¢°›u>$¾Ø-—\«ôAOz\x8ÔDUET7ÃH#bl',9Ÿ:âœ2sa§L"€sÆEÛ„1 £ý<ÊÈ@œÈR}^´ŒÆYP‡œ‹r Ÿô`“¸êstá<Ï™Õ)Ç~£™úX3ý»³þ}` àaøEù̧¦…‹nð“˜ò ¡½¡›ù"íK#ã²ÌB„à å‰ÔȤQqE3óéÜõ_ÊnüòìõÐ/Š 6äPGÔ«‰xÔàJç"&71pdB/<Ã`×õò(ôz>ŠfÃF·}2àR¸íÓãŒ×AijÕõyrÌCÞp;Fý®Y—}܉1ب~áw~§ŠF'PÕ%Ò4ñhƒn5®¿bP|J£ð°fÅgØÒ¥0¯ñóЉ_AÁs‡0 â\¶8€çŒ?eñ™O† c X4W~@QÏüÎeâ]ò;õ‹œFÔ:Œs&å¨[àö v4ê\ÄQϹmÆ«õ:õ SŸBXHqÃCx’%Qý8i]pÓÃy>¸qzIq9(Xœ„е«œ„ÚnžÇŒ3Æ 2‡«<Ä’ WYµ€C¤qÓŒD<ø¿¹ ž aN|<»tÈ”LR©˜f4êF]˜B3užÐNú©%./†ìËÉÃåL5êÊxˆZ„ï$…F˜)‰ØQ=üåQçéFáõNùí^õÕNñÅfáÙÿÇžáªË™gÛ…»%mdAÞ7ží–oÂmæAîodmåžïWßµžnUo”^î5Þu_ì7oW@žíÖm—öl—ïo–î¬åî®îT¾¼¿òÝÓïŸmž…Ãqã6ÿxúÏoNÿþõ­¿}uó¯÷!æ_ßßòùéõÞŸÞþÞüüí³È#@£¡þÝP@ÿ óÏw§ÿüêô/oŽþôòàÇ'Û_ݼ½Õ}qØx~P¼]ÈyyÔýáfñénõ‹Ó(¯[oOº7»q œ‡[ÅÓåÔa+rÐ A1GèðO+r³ß©‡Úñ“åô­•ÌÍAúæJúÖZæöFî°=êdžs5в¾ºÕþúV÷íQçë“Á×·–ŸmUî²·{©'[¥×‡ÍšdI2ªýšôh«pÔ ¶{ui³ä^+rËj9ÇìAG_äj¬Ÿs®]í sir’©—svr\;ël¦™RÄVKQËeÏrYXoHÍ “ZúE®WîÕÞÎP9 éæè^ŽÚn'χÃ5h#ïÄe“¥X#ÍôŠ®–TK-hƒ²š"Zg·ìÞ¨eh8läèõ–Ô+»@Ô<Õ³Q ÀžŽ¿_qç£PPñlaäN`PÃ.mˆÓæÖ”-ÇÈ„×l“ôšÛ9×Á µÝ‰BMC©+5©[òìöc™€Úr>j+Ɖ¨I…,;+ñnÍ»Þ÷›R9æ"˜ß£KGq@ À  BÊ‘‰âšó£‰ V/¸KP )'ÐN!Á$x6J¦BvÉ©]m„VêÁæiæ<™ ¢;ÄëÊIª”pô«Þfž‹KÆrЬ¥™nŽÙíø÷û¡õ†¶¥–!JI<6z¨5%9ç½ädʧ®&ÐnŽèfíû])'éCìp5 /9ç¥<ÔN˜u× l E®SøŒàTØD)C&&³,]v˜FYë”—Vä"x&„ ”‚0Žq6ðb¦htš0Lê€_oGÆHtZ:i‡’)'‰°g8Ïü»¨W›¢„q’¬ºë˜öZR²Ä%”§ Óð‹Ï„½zðáªüNð˜f è Ÿ 0j/¡@/SÚÑ£Fà ì¹Õô¯Äí’ñºgéóý²óÍ~öï¯7NûâÓäÏú÷6¢µ¨1íUf%uÊ«rh/KÔ,ƒ\¶kÎÈ—ù:©¹€-| âAGDlŒC®Pš  còOÜ–1Lq]ø”Ð^¥ô7|¤"ä\²,^¶i®.ª&?2«.SæI¸0ÕôÇ|×\àÁ‡Ó•Ï«Æ?2Ê.@6ЇŠìpŽqš§ý”J 䆅‹ò±ßíÀ ê«ð;"­ô’ò„d('†Õ22É$%½‘ie¿›ïÄ¡ðoxÈ)Ò|-&jZ*é×Y4pÝ%Åį“¿RMÿʨ<à=Ž™°°òÌö8m×áD8ÝMLBÓÎ}2ü¨jø2nÔŽ\7.~†j.røt€S1–qlé2D ¤,àRaÚËšÙàp¯½Œë®:­Spü®Á–®ÃS¼ €U]YšûT;nG;÷™NvÞÇ.Ï8$îÅS~ÂKé±¥I™uÙÔÉ‹x܇Å}–b‚à mS(fÎùÜÆDˆ`ì‹Vã”I;Jb2 2‚›€Xæš‹³Ùãr,Q˜‚Æ•TA[Uª®µÓKÊQ½zÊnQ¡ˆL³8¦×LëT“ØíàgØ£W^WÍ`¢:ÛQ øg¸£–i–Äìæ9ËÒ˜qñšE3JšæŒ‹PJP&ÃkÎqghTz!€ÆY…j¯ÔÎàq&Ižõ,‡Î çëÆõ³çU£¿EfΫG?Š9‘0­]¯ø·›Æ8òöNçÕ­æA7tØ —èzÙ“äÔ·W9/"á3qz±,™W²Î‡ëé«ñ­]‘ûƒèÓ­ÜJš‚NíÎJòö mW]Ž0šû;•V$) õ„“2MèdÀ‹$C¸×©Žû±˜dì!Ix-ç4#QŸ…Æf‚¼®SöÞ=¨œî†ëºçœ½"_ŠÚ“’9~_’´‚é¨=wÔÊžr÷{.Få$iB’Œù4ŒÙ§Š!æáïÊ9g9ã¬d¯1æC©\„<ê5ƒ»ZŽãí<•ðéB‚ö E¼º| ËGPÖr#ì’#¦kbÖ;å¦THØ›g.j ‰ºˆÏàskü-1ôêë%W)MÆƈ¨ð`¡j ï¤éz‚Hxu!N™ð!é9îGâóéHó ‹ú|'O?ØËýðbíöZ|µ(T#NÎÕÌp`öóA|½*­—¥Fœ¦µ£iÞËež°º¤.r!à4¸p¥S@H›e~)FÙ\ˆÌˆ0gðQjè²kÕj—}š0^Ù…l+Ä—ž4ÏøX}&D8Qæy§UaÑÜÀõcÅ8¹ÓK®”B¥] ±•™æŒQBµ‘ãßÔžmæ^ï”@ž½Ïz¼š¹?ˆ?ÙÈ=Û*€¼Ø)½Ú¯¾9¬¿:ûbýõAõõaýÝIûëÛ½¯N;¯ŽêÏvŠO¶ À9Ï·ë϶j/v¯ö[¯Úo;oŽ{ooö^u^BÎ΋ƒÖ£Ú“½ÆËãÎýíâÓÃÚ“Ã*„¯nµ‡³tŽë¯owþôöpåç³>èøç~¸ À¤žáÐí³d÷?°Í¿¿¿òÏo‡9Aþòîø¯÷þõþîÿþøè?ßÝÿÏ·÷ÿýÍÝŸ¿¾ ò—×G¿¶óþþêWwß>X‡è)ÀÞqë«Ûƒç{õ¯ï ~zºóî´ÿå­Þ·÷×¾8éÞ_Ëö“'½4›øq; À³×Œ÷RwÖòw7‹w7 ·7 w· vËÃO×—ã‡Ï“â·÷ÿüâèÏÏvŸn”¿8ìüññî›ýÖA%¸Uï2 _·öª¾'[Å—ûµÝšo§*wÂwW%wÞ«K{–€yú9ZñzUΩFmë5qµ"BørÉs8H¬V}«Uo¿äêæ¹õ†Ø+8ë×_Nèµ×ª|3I$…¥z¯F±­¦ØËÑ<©hæùlÈñªéá$áõf`o%¾RÎOÎEÀl q[=Kƒ·Ò«ðËÕ¡tŠÎz–’O[ê”8`žRÂ6¨ ÍÜðs*PvàŸ­nš$E5éì–¼6s®b”*ÅÉ ÉGˆÕ:°G £kiçV'¾»œ„üP6<ü²~«?ꔄ¸µf°]ñ‚•èÔÄ\’ °Úìƒj‡E¾W—Ò[T2× ®B’ÌÆ `žõ^Î ‹`´Éµ^àáõÀrU*%è­nìp- í";%O-ã,%©fÁÝ-{ëYgöl%ÃNÎÙL:Ò’ scøÕ<]Ë:‚ÊELºS>§Œ·O$¼êz o¦mŲZaÁå‰ ·Þ›'M# 6é&evцN¸(¹N+$9_ˆ 0,‚Îi›Âõ×€yxÇ<K!fwó<—ÖeŸ§-B*‘V‘æ)žÓ´÷Qž\p˜Æ"‚¾œt ?ƒuk¢g$ˆH[&]Ä<°“Ë>—YÁ1ô2*À ›³¼ôb˜×Dx ¥Eu S_¦nëœèPô²Ü_^îýphcÒ>/SÏù&Ò Nõj€|Büb:dñÊB -Ä-õ,‘ Exvæ/À9â²O¡êó‹S¿°añkœý𗆧?J/›TŸ†á[*Ò4®›ÿ°ÇaÕ/|ª“}bÑ|Î3q ¹µ r6z3á²ÍÈ ÐÝöy›Âu×Hã˜H/?—3Žºló6@kVí`ð~áŒA ¦¹¦›?8L«s˜¦r)!ÚÀ5Úa, [„.#â±ä#LÐepÙå!1Ÿpƃ„è2à¦é ×1àžÑäиBT7n5N%‚D·æÉ%•;íjWÕ2ËÚ—l&fáf9ЩMºYdiÚb’ð,Î_BuV€ ȇÄä.r ؆µ+‡dš…€Ça¦²6%OjËÂÙxμzæÂÒìE‘Ö‘Á8 ·ãcôå„ ®Èp!œU^|B´pV¦ºN›¦yë®aM3¬y6æ2”CdÂe¼µ’’š´`~uÚ‰Ù“ÕÄŸ¿8xq³±×vR´Û<Îè®´‚1fÑeÚé&È~Š~s\»Ó?ßJ/'ìG ñ¤|µ_þúN§(šmý¶Ýi†q&Ì.®€¨n]&jO­vÓ”Ÿ7–R®JÆ#°:Ée¬æ„rÚå"UAÞÐ*z:àuv"© 1Ìë«i6!YÊI¦SÁ­÷¢ˆ8¼<Â;µ.€X?.¸ô’`\K±5µ‹n­MÕ œ†§U£‘8Ý  ¸«~,èBR¬µ¦†G|’ cÀH„e¢œvcXJÒUx6•iØB‚ðñK±€yµZnù!s£ê ˆˆÈ/ n5KÉü¢¾¡!ÆMË‚.ê7U2tF2¥Àå Ù š c¹ˆlN«èJøMtŒ´Œ³g¬n×]]­ <u°ŸÅfÀuÝéÇS~”·Í%í⭵\7ãÚkÇò>Ì‹Ëâ.=”ªp¶ hR´œMJŸåqy>HÄxSFÂ#.D"U)/ZÑ9?žL)Ñ”µ–¥ 6Ã5–‹¬Š³§ÙCá§‚$OiÕó—ÝyJ­_¼Œ,~®—ÞÍ‹ëåÐV5Tòáy·i9AßéÅnµ‚_7¾=m|qP~µS|½Wy±]¸ÛO<>cžG×\Ùç;¥»å§[…¡²Wøy¹Wåùn€çéVñùNíÕ^ëõ~ûÅNãÉVõùNãÍQ÷«[ƒ륇å';u`ž!ðlUlUïÕŸ·€s€vž7†““ï^œ c~x±ó—/ÿøæà÷/wÿúÕ ðÌßßG{þõÝ]ž?¼9øÿ!@¤þû‡û<ÃlïO!?œõÃóí¿uú¿?<üùë;ûâä¯oŽÿþåÉ¿Þßùó«C8æùîÑæO¶ßß_{yÔz¶_sÒùþáæ››@hw§Ë>Û­>Ü,á¼>n?Ù©<Ü(>Þ*?Ú*Ý9ûÆêÖrêt=]É®æn­f—SûÝøN+´Õîu·×3'ƒäI?ñþ>ÐÎñÿ¾¿ýûG›ëOÓOÜ9]ýööêۣΫýÆ^Ùÿr¯zÒŠœvbGÍЃµÌ7['ð öEÉ— [uïz™9ÄVŠî•²§•¢OVSý(TƒbضÑ ÊžN°‘"[Yz ò×¼kUa¥ÌŠ®^žéd( œN–.‡-»@# _¿È‡yh5ÖLÈVËpÐãC#=\ÏltÂÕ]MSŸ½žeÖZþACàiä™jšì•Ý­Û-»úU¨¦]t¦Ãw[ý ¿Ú×Z¾Mp%ªžáÛ®ÞÊóͼ§‘áK &&Yl×p ,– ÚÁ²ô«þ~Y:Ù.ß?hõ+>`›û{®·ƒ›ÝðJËF8gµY_Ž÷[Á<\[Þµ9H­÷cý†Ø&ÆãKÌ’T§*‚øy}ÌoéÕý¢'À">sP0€™JøÐ¸ˆjþÕF°[Y”Àp˜+犉¦LO¡EÓ½Š˜ Ûb‚q­&•£6?½Ø/ñÛýp-KµK\:dâÉ·cÚKϳøxÒ§ëÙAÉY‰Añÿ’žó‹~NÁ“àÕN€ÁMã`‹Üô"(àUIàˆ‰Ã×O”eÌí˜õÒr Ÿ ˜#)Æ ‘Ytâs¤y’³Íó¤PÇï§~ø žÝpì¾Äâ3 „}-ÃÂ)@;ùa=Ëú]Cìø°GB‚N kÕ]±#×hè°é¨€Ïàºë&åçÀE^JÁá³ Øô#ÆÅ‹p¶t•±L†QD~^ i€vή\ç0N¨¦gÓºpDÎüV';/±ZÞ¡âpy€3]fW†9‰5z)½HëÃ<àŒqk¼ýZ tøÜF§ClÃJЇüÃ!Ñj —–yÒºðI¥ Ïà‹¬U!¹PWQ˜B`Œ<˜’#Ú¾dÖÍšõó€=Â|î nžª¡qCÝžÔÄÜÔ’Àê›’s¨ÄP ÞÇ%Î>8Ìpø" ‹É~@‡Ëî–@k ]¦I &£Ñ9Ú<ÃÛ"­qZæÝ8g™²:žZœýþÙÁa?“­Ë9ñîfe»îå¸AÉ] [Ëaìûg›ß<^;]M¹LCà¹Ù‹.gÙ/ow“NuŠÓ4#¶?½ØþòfãëÓænɽSrð<ÚÈìV…fÔþý£»kÙõ²X ÙÖKÒrÎÓ˸¹¡‡M° uF&¹ ©°#"3Fd?o‚®ÖÇ!M…m…-rP÷À¿@üœ6,ã?jŽñýÍB,`‡m^·Žç9t”C‘IÒNJM"˜å™O2„e&è5¹(%X³rÆÉ“‹"«‰xM!â†âÖ5³àcb™0 À‘²€€TóÎZŽJîçä@;í< .j!1üʉ¶Ï¢ÈuŸ$íÓºhmŸ¶¡£!ŸžÀÆ“K»æiVÜ~Q§U_ðpJ–˜u #¹°\âø¡_J bk9ÒoŠaÉf£±)ž’ƒ'`Ÿb.‡,FÁìÔsìrU”œJ1'8dÛíxå+%ÏíÍÜûGk;€€Ng=ˆˆÏ/M~dS_1Ë΃"às8оò2(aVÃcsÄÒˆh—§½æ”`Žºup;{+ázŽF—.O^ýȬ¹êãt„y&¥Ã>+À´žd3z²œy¸UÚ)‹ÇÍ<å[MéfÝûd%öÕa勃ғµÔ«<ÈIÓÿ`50óâlAÂg;¥;ËñÓ^”gÛÅ!íìWŸnoæžlžläî¯dn÷Òwzf­Ažú½•üÝåÜ«ýöÝ•ü½µpγý&È“ÝúÓýæ«›½wVÞÞ龺Õzy«ùúvôç7뿵 ÕõÍíÎëS€Ÿí?qø§·ûêæ?¿9ýýË?¾Ù¾ízëßÜú÷pþóÿüxÿÿøèï_Ÿ€@ÌÏß݆ð??>øù›»ÿøúö_¿¼õç·7A@CˆüûW·z¹ÿóÝ?¼:€ðl-ÄÞ×÷V¿¹·öã­?½8øëëãŸlü<Ù©>¾•+?Ø(ü|y«ÿò°y-ÿl·v¶GF€çÖ sº–ƒæ¶Zö.'ö»Ñ»[ùÍšï°}qXÿ󫽟¿¼ùõíÞÛÃæw÷6žíÔ»QÆÉVÒüÛ£îww×6sÂí^âÐN#ôÍíåçÛ¥ÓNt¿ê[NÛa%Çž®%ÖÊ|ª\žÝ¬‹íU‰Úú×rÁ½VõB§¼Zõm6ƒk51%è¶šâjųݔÖb5Axo6¥½~”zj¸€LZ2#X¯àÚlª)¦”  ¯Kh¯êÛ_ËlöbËu_5M×sÎb’h—Ükmi¥).×èî—kÂ*èUH»Èõ*ü .@ãZk9g³ ‚ÃÀ­¶´¿šˆzM¥„3ì1ó¤zøŽ[Äü’‹Ò™ !9õ«ÍèZ+¶ÑŽêá^%°ÑŽ­6«M© ÀÓ måVšþf¯å]½ºT-¸K®^ªXƒLœîÔü¹8 vB œ¨„‚’ áùÕ* +íP>A‚Ts@Q2d-ÄÉvÑÓ«¿¹ÀçªfØrІÈt/ÄÈJŠMJÈJ#¸Ý‹ç#D¯ ô‹‚D«2K»¤çŽ`ë¦ÏˆˆbÉeŸqáS”i¤ž"W¢™€®A¢ÚCLæ"Ã)Ö„y”´N9°i ˆÏÄØæ:MeÒo*§Hõ9qi8DÌâ“„i„0W÷ú0C[¦=¤B •6d”0ŽÓØ m™d°IœêrŠÊ„ <5qÉÔÌs_ ä Éäµ"£‚ŠŠHXÐQ踇”ëåçg# a^7\©`8•á°?­ŒñzÁ>—M½,›õ~ÛÔÒÈG¼i¤$!~bRÀFYär˜šÍxU„æ3³ì#·åªÓtY´ßÈIê(;›5’}<Å+:)<@L%y¥½uÊíê 1aåaFŽ+Ï;#6í#æXÃ(­1Ì}‚*.D9DÉÙ§.l×\UŒ}dÕ\ãp®…þ[~zL!$2#»ökžqcó‚M´IÀ9u!àúHe”G캒 Z]6™Û.ÏZ¤•Nâç†C.à“"ŠsqŸ!êÕuK\Ð¥4MHúB+Æ­Q:)e»Èä£(m¹aP~2Æ1]è9n3¨‰ŠrŸsFâæê9¸×ª»$óvä0Ì1©.Ú‘ç‰!Øð„ pȪ½F£“An œ_Ê<‡€@ ,Í}ØcV]‚œN+‚/O*Ýö… §Kù1`]¸(Š¥¹s28-H5‡C65O,ѨBbM©óR:@£ÏGéµVtw%3hÆ*YA`uvtÎq6'9$bÀ6@;"g€x !7½½ZØÏ)t˜ÇŠL³65Oéün”@¬MÃK.Ú 8QŽ2V•Õ¤°¡ ©õ: Fg3ÍYôS  ‡µ+EΜcÔŒ±„Чu$ºàÄ A— ðÃX¢!pŽ—ÒÅżVP ìF€3¸m ‘RK´&ÀêBN½Diz±¥»YAr¨iÝdV´vÓžj„ Y!€¦½HVÜÛÊýåÝÑË› Ÿq"#e¡µÝìGÓnm„T$9ÍÝ•ÄNÙs«8jøNÚ~Ðï,Çndžï•nv£ÇÝèé µUóßYÏWBö¤Û€«®šUW0Ý‹/yC\ÂC^,豄½Ö€ˆN$*Y“!{:êhUÄNÍ×){«gÌgþ`*9®Ûð:!¿ O‚›šApih‡Là—⼜we“´ÀiA"’E€fžå€ÀwËÆ0€:¼{o5 æ«§+ÅñJš¾™b•ðƒ`úòC»ÍÕ³d+GgCæ„ßYB¢1@N!pr[`‰)=ëãänr†sLTŸ6 ôóÇw'9Žš1é.&ƒ¦vŽ=ÙH§%c¿ÌW’g›ÚDÁô5‹ÎlÜ´<#ç‡ëc€ÁA’A‹èVI^­IwÙÇ)u1B%F!Ñr˜;[ê_›ñ¶À/.óEÑôÏÊà•ܸ­0¯–\Ñ ó!Z¡™øW_’Àâ2À\u-ÆXË4Ò·OW¿~¹ÝÈs‹Ó_;÷1‹Ïaú1›cír]Šû­µŸ‰;JI&ÀE›,É#(±QòÔÄõ,}Üð>YK¼=ì>ß®¿Üo¿=î?ÙªÞämUl”ﯗo×^ußœô_wA^<œ´_ž4jÞÞø|ýpà–¿¾;þþùÖ·O×vþvvøo†³z†xÞ‘À<ÿþþîÏßÞùŸîAÄ¡aê»Ó?¾8øéÙÞßßžüÏ7÷þñåéOv¾¾»úÅ­þë£öÃM¸…Êó³‰I÷VsG ãÍ~z·9YÉÞß®ÜZËö+%ïzE<ìÇÞÝüøtû§g[?>^{Ü|w«û——G?¿»ûÇç/ZO¶*/ößÝ[ûútùÉfñ°$Ù­‚°§6²®Íœû°!wCÛ5áÎFz³&í@}تûÚiºÄV+ÃF·[í6ÀŒ´Õ­7¤ŒÏp¶–s£)ítƒP]×¾n¨‘¢¦–$³4!s!k·Èou#Ehƒ)Ze=ç*%éJš¾'Ê»Û%¾œ¦{5agYmK½ª»šq¬4ÄÁy|ö«žõ¶P÷–â6ð—~öQ¡iœ ô*BÂ¥DÒo+'¹~-TËzÀ· b2`‡øAd÷LÚE_«>©¸;ˆoö£+­@=-x†P \L; )¶Q‘ºÍpµè­ä½ËX¯,¦€œ°hæñ¹´ñ6h…r â+Y.Æ%O½À ¬ªžåÚ%a¹&uJB!æÒK­µ,WËpÙÿ›ÆlîW¤^ÅWϺÖÛ‘ÕZ`¹ìKKt:ÙˆìL9ÅDEƒèT¥† ‹d 8•…0Ö-°Q^ó.fCúˆ J ^vÁM¬¢”¡» ©’u‚e¨¤é~Í[N:À€”SD³0:+&l^f±NØ #:!P Þ±ðrjB râ3Å8QÏ:“~4äÑùXO6‚ƒ<ƒ(®Íª»Ž#×ã’±’"jzö|£ä´ÎdC8H%IU€ñ$4îAâ‚!âZr¢“>ÇÂvÓ•­ êHõERsчO†™˜2Î}ìB¯3†Ë¤ö|€œJðrÎ|‰Ô}&â#I÷‚ÓpɦüUñq”•Å9¹˜ô`£„æsB}WžæI Zëâ9»ê’›r£S”n„1Œ’Úk2â4Oºk”aBTyÉ®á°9‹æšzæS“ò2:\Wçúðë-ËcšBæÎCz]2É/9tc¢CÉ¡³€@Z9MŠK¤a‡ÃçHÓäÙË,CbÕ~§˜G €ybŽ0ŒÄE}B2„xU-CB¯á¥ç|‚n%û´AÍÙ'Y|ÜELyé¹³U¯òsQQ Àã¥gm†+¤i„4ÝpÛç9|Ö¢¹bP\0*/Ðè‡OñŽyàc!ÄëË\ ÐŽ}ø>k ×] ‚SRüCF|HÓ`ÐNR²öÛèdç5³Ÿ"ò‹@;ÐÕÒèp77±^ïP†YÏ ºP`W…ܨÄ$IH¶¨ ¸‘jV(¦ÜàùÒ¸i­Í4 ´#8õÆDDÂ!’±/ÿAæá«Rãeôÿ©÷`rÛȵÿãûî]ï:+k492ç‚ ‚ "Áœsœ!'ç¨Ñ(˲¬lÉöZ¶œÖ»¾£ºõ}U§ºÔ@Ÿ~N£á™' hb¬;„·ÌÛ×9‚y4.Ë´vöå7R¸Gæ-º«†¹‹>ç éÓ¹-cpZJ*=ì"1´¨±Œ…QÝyÃ<»†pÍâΙDA-¨³€j±°“õ€ ™¶¡–1Ÿe4„ÌRÞyÒ= šr<¢„m2i)‰hšqrÈ‚BZ” ¥À{dRÎ`QtÈ„¦¡ ÷k{ÝX Ÿ—°YÁ;E;nœ­ffÒ¤~«Î¯—¨ÛÃä^#2Haër¯Á¯äˆ¦äþz¯2])El›µÈ0O­W"éq˜/gÕ&8Í×µJl%Цa)»H9˜€ 2VæÝIɡ-Šè*e­Õ(IÉã­o¦ˆY‰7-÷Ä~IF-›éa;ÞËû|\²‘ØlxŒµÈ‚3Î;Ãø10q ‘S=GÔ²D:êlÉœä**‘œO æZ.eM~d"Lj3 *ІZoÍ<$Ïà31Î\Hùû-~¹Y_äöÖäZÎM¸.Ò¾ë¬{½ClÈwöÒwr[‹‘7˃¥ðº­aôp-¹µˆiÊ7êwBY¸Íç#Ä\4¬ËÉî­7P–ÃØ,©ŽUŸP8›@jàŠ1Æ$3f¸yÕy¤L<¡'‘iŠ6>‰•ˆ¥”D%Z'Q:xðRdÒh*ILèb!Ì€$Ãæ:Ëc³">v +¡³µdE´äX]Y4—¨P,JhžlÓüÓ6ó?ŒûjY27â¶|D#ùopÈå€ñcãÈÿ óC5¥¨¹šd‘ü2>…¾ ;¯Û¦ÿ˜“ZDû9¢ýÒoº¬¿ñ¿ˆFU¬SÙfÿáÑ~nŸÿ·]5Lüïô¥ÿqj¾À,#€:vÍÒ=Fçu£wÌé5\µÏ}aûh5^rO'»LšHçÌb¦ë†‰/ñ„V$u 6+Ó&€¯ùªHê)ï4íƒJsò¨ÐB],Þ=êw\zFÁ„ê§þæ1]Ô®P³a OÛ%ʧòO<¢—Y-GL«Ã»B3l`”D/sÁ‰06Rçjžˆfœ:µõ&à³i>×OªkI£Ö«nã—ˆù²iöcà7§î"„À`ð1Û €gÃÔ?QËõó¥êÇ€a,óŸØÌüiöÓ€sB(ï,áš8oð™νn×þ2¤T{ïxçxüöi\í´c—i´GCÎ$‡²¸‘#ÌE…ÈJhJD" `ó¡ßêœâ([ZÆILˇí4a‚·uÜë˜"Š@ÑfÄ<ê³O`öI¯uÌ¡»FŸ·Ò`NµÅ†ò«äƒ8g‚˜Áh,úÔ9 <£žÁ> Ä`îY¸æšµF¯P.Ç&E,) iUsPxMð…IdAZÉó<Ü´ƒ;¦Ó(ðã×ù¬ã4ºæ= ãˆ`Ú¼„*b#EF|×K¸!AÙâ„9Ï!rÀTaÑÀÍTeÏnO^®Ð;]©ó,åÉ^/ñvÒru¥>YJf)#˜åízäÙAýû;‹«ù`ŽÔÝ[ÍîÖ9Þ=vw5»’íÖ…ÕÝQ|É .EŽú‰·ÖóâÔ]òZn€¿–âÑ0€\À$680Óf¢V`dÑM:šÔ瓾jžÈ§PErpŒCGir¶^ÄÜjžíÖ^?n*ß<è禮WeHÌ… ¡eIµÉš%µ!ßt$¸@㳄g<s¦Dî¼£u3ý*Õ*dÖ¨pVYpÄg <Ö4GÅXSVvVÓ(0IAA²Š¬èÎz¶’A*IûîP8\“ê)k'븵)ÝßO>>ίñ[]r¹î?ÛIù€¬t«ÝH«èïW ÅÝ—„Ð[éÒKÍÐú׫†äˆwxmWž1–Ô¬‰£ ¥,¾Ü3’SaM­‘ä,ýçårº[L‹‘œ[­‡;Y_+‰nwE…Òz´Ÿ"ºÏêIäñI£›ÇïxîIZo·ºÓ“•©™$‰`–wkÆÔBN݆éÖÚ⿸õä¬w¶]¼³WÙ^”eµü.€ ›*i"¾0k“ÂÆXØ„Ÿ¯K¸X$A¿³dßoq‡ýXMr&ˆ¨j¿9jÝÛ*=Ú¯?¿Ù{°]><_vêl9sz¾Æ(ÐΣÍòãíêãíʃµÂaöÞZImY.Þߨ¨ý“·ëP­?9èÜ]¯ÜÙ(ßÛ¬=Ø­ßß®ßÙ,ßÛ®?>ê|÷híÇ'›@å§§Ûï¾Ù|÷tëçç;€1À-|{x9ûïŸïþülâ! ° ÄHÿs~}1ÀH”¿~¸ºšLÃuóoO?P (°ùï7·þ|söëËã÷ÏáÜ>ä‡9ºÈØK¬#ÏX7ªÜµÂvC„R¶UçºIl¹@ž­fö:ânOêeñŠäìçëun«-®Ö¹íž\–‘’ä^,3ƒ ROâƒ*»\g+ ÚÉ“+M¾¨ iQX¯S¤j›†76K´-'cÀ Å$žàn/( t ²TGá³¥ˆÀ‘q‘ÒÑ8Ô¹#b:Å™V/Ñš£‹³&+بch=àJ6掳V ŽÐÄh“±/·D  à¥(¥C:à´œìÚaüŽ€’®ç‚:`$žÔ¥D;F.8›àL…¸ n\­LÔ à·Þàq-ç×$[žwŠø|–³6Ó¾r̹Ýn.Ë6ƺ¯ èhŒ˜‘ˆÚ}võòþ$­Íq†"¯Ï²š<» ùGxÏeÏÜÿâ†e|¼ÑÕcÖ’`ÌÒÚgÀM Ë%Çì?XdÕrÞ \cnÍçöé–ë,2å7]ÁL— ³\&œ#¨é¢qòonݧæKÓ̧nýUD³3êÖ^±Í~nšüØ>û¹*z×ðÏÔÅÿ5ÛXGaSÀu¨ášßrƒ°;¾Ä¬×HdبB‰2BðÛo®q ö/£Ök'`6}ÖëºÉÿã³]£ÐIÀ0ÎyÙòŽ!æ >)ª„3/3GÌŸã®+:p_¢ñ "Wxr^¤·.| × £3p·áf¿æµ\±k?%„L¡Ö»æK¿}³âÎq ¯ùšÛp1]ƒxóì§šñ¿CX}IÎtO¦?†4iÁ“¼êæÔÇ€=.u© ðY†}Z™çæ¢à…õ ¤=ÅcåD(Á!9 _¬EÛE¶–ç‚q€ŸkÆï™ ú´I ÛZ)†üzˆ–4GY'pQœsg$@&]Ð3C¸§#„#LIqšFÛD™gI‹v>Ç:ºfè ÙçœAì“8²@ Z—y̬½Ì1À9¨sÆ¢½ŠØ&!=M˜!äHøtí f£:¸y´ò„ð Bˆ!\s¸®;Gyµ¤g!à<L÷\™‹QV!h‚ÂXŽãÝ<Ëa:Ó²¨Õ_¡3©°#ìœáЙԓŽ`lp9v6šB‚Ô-%ÎV³ËEª!#í„o˜‘æ+÷6Šï­­æÉ¿Þœ|½SImo—ˬùåqûöJz¥@Yó ø‰¼}§)vbºÖŒ±~×<ôÌÅhg4d³HŒ?ÈHaz–´*` RÁD eBF —g‰v‘7³´–$fä¨e¥/ì¬È·öro¾<¿×ÞèÓý:¾Ò%·†ÂÚb´œ ¤¢ÎoŠ@Æ£4˜}AñÐþI›ŒG ஫5´–8\K éGí×hBË QF‘µPøæáÉ(þà¨Ö2x\° `ÞÕéÔlI~¡_AonDOÖ…û{Ê“ÓüáÙéooI<ÃÚÌØ 1ÃÍm¥˜°/·™fËÄ, ÁPJÚ«W=ïÙ_‹µŠX=þ¬U`LÜ£`©RQ7x¸¥$¾5Hm-¥baxÅúb¥Ð)™HVð‚Y¤4Õ ZVܹ¨=%ØÁC-à…b¨þbÆg„à\ŸF- ç¨Só¹uæÓ$kogip?ýŽq!d´Ì}Êb3Ïî,þõþîÿuúî鯾?~xP.FíŘ³(»ËIÔ}÷¤ÄÙK9²V¤xʈ;G¡†º¹‘ß겟'Ê’[ éS´™õªk K„VÀf†åðíÍâÍâý ϽíòÙZ¾–;kù{kù‡<;µ¯wë_ïÔïTíÔï¬ÕáçêJ蹓åÌÙjñÎFéh¹µ^x°×xtÔz¸ß|xÐ|uoõ——j Ìïßþû­ÚJóÓ³í=ÙàùåÅDžÏÉ|üÇ›£ß^@ÌûÀ<;6!1Àȇ¾: þC×Pþú×­ÿ~ É~ {Op>° (°ùçÛ³ÿ 0ˆÚæóâ"{uó—g‡€@?<Úz÷dïû‡›/nßÞ_°×êndƒ+íh»DWÓD=*(XFF*¢´-. …T°œ¡ ‰@=G.·…a; <œS°F™®—Ù”‚BÁ)Èj>XÊ`ù¸[æŒÀç+í8¥2h°çk=ë½h9á}s1¶X£æa³_¥W;„Ö0hKkƒÔR[êÖù¥N¬Yf’1$.8ׇ)ˆï6„V•MÇQ%êŽñŽaO^Ä—Ú\9‹wjôR[,eðT ½)À$ÞÍ…íQIʼn¤¬J!M·«ÑdMǰf1ç\…$Þ­qÃN¬[ç–{8Qý&×­3ý¦º®(H[ƒÌÅõ^ËD=k]¥S‚‡ ×2¡jšê–…FŽHéšL°`ÓlPʼ“D§À|e¤^$eÞcÍ@ƒYÉI¡ã2N8¯5óþrÒ“̤w” Î2Á9š˜‹‹6‘1fâ®F¸Ü1c6fÍDM)ÁPˆ;31;œ»¦ w)œ9©®OâHJtpA  ‰6±0ñˆ H&)ØeÖ ·Dcó4¶£íp«iQ%7Ê7Cx&üj/ ±£'‘Ñ”h©¦Ðœj°wZèj <'¸›2ŠkiÏDœÔÉäX€Õuw#ÝN89dÄ1ý7ÊyUÀ¦Ì“CôŸÅ‚ót,œžkÆí½”SÁG#Î/‹‘…2¯mÊÖŠhŒáãœç› çPýç¸ùbÐ~7_v.|–œ¡ÃÍ×8ïœWw‘rŽò¾Y u-xó… óºGÿ…Sû™Sû…hÇx1ª‹ 8uWìsìs_8.€èG?ýìoÛí Æk–éOYt¯á*0¢x³_/$Fϧ50~@‘]z¼fu¶j4°þ› zÆõSÿÀ<¨õòùLדÓ—al3Nâ5_§¼³ j¤Š=×qǸ]{Á¡½‘Æ©¿ä³Þ@‡ŒWÎ×׎d™ûü|ží)×Rè<Ô¶¨u˜BqÍ1Ú]T¨jŠID`Ü1 _…Ì:¢Œ3¨Î¬†ê%Ò5òÚRP8°"a/Ø„(늱Ît)*þ8„>±Ç9g)EÜÓ¸{†bœKæ8IÀ»ÀãsÏq!›Ì!É(ÆQv¿{.ˆj™s¶ Pû‡0½Û2n7Œ´“§ìà §~Ä:Ùk!š {À†õ?´J!&@ÍqÚ§…° O¡B®ÀÏEQxØÖ+r+õМƒÌQÎÒ> ˜Š*øt¬g¶%ò¢ƒE&«2ÒÏ“[í¨ˆÍ®TØ^&ÀºÆ_-mÖy\Ñ=óI=†ì4…"m~¸Q¸³’YLaÏ›gƒdWAß?Þ8îÇ«‚#Ké·ªìR&p{%sÒODѹf&¤°À9VÓGp3P}Øg ûMtÀ ÏH“fµó¬-CĈ X(¿Œ‰¥ gJÆÍJðænîñYcwI8ÛNÞÝËôÊèæ"]É8óŠkØæwW3Ť—D'À,„ÐqŸVýÍ:µÕ—j¤HÎо‘%ao9¶=ˆÇ9 k u(2 —h#ÏæÎ'*G qŒ:ÍWÄð|1aíW°•V`³KÞÜîîÄ÷–¨“5þñq¦³W“æÅ²77žl&†-jÐdS¢•#ç*¤_ ­õ¸íatØ S¾1()4:¥t…8ª.Í,¸ë*qÕSd5E&8gœ±e$\iÜ5Ž9GYbÜÃ|ì¿…ôOЬî¼ÛóU™n”†Ô™µŸ³àHú&Û¥àÓKÛ%+¹Xl³Œ ÚžŠ¸r1_”²è&ÿQKùÿûí¿ÞýùöàõÎ˳æ^7R;I$§®íÿŽ@gÈ€6•"bQ7Eê߬Ï5Jz§ÀÀ>€ó˜`­ÑÌ&°RÊ;cýD"êIßJU­Äïí”ìTŸ·_ž->9h>Ø*ð<Úª€¨í<;µÓaæ §ÎN|ºš?[+܃]{{õGÍoNû¯¬¾y´þòÞò‹{ËïžüõóýŸ_ìýª Ϡοžlþôlbþxsüû·Gÿùþêƒi~üfë——û œwã9½€7 ›¿ž£‘:óÛH4õëË£__žüöêÿØyÿüèçg(À‹É.@NŽlW™¥vlm˜^_Î “¥|hu˜î5…L/C/“¯´¹fI]‹¼™'ºe ”JÊ×.’ýÄC¸Þ“`³œBa³ž#Ze¶^¢ó)¼˜!૆ôŶt°]Ù\Éö[Ñf…Í&°DÔ ñ€@+ýX%‡ ´¡š'6†Éz1]1ΞIà1Áå<1 ª$bDF!Á{E\ É'ˆ|ÏÆ±\KIH1¨æC¹šŠ¹vÂab¥ž/µX¨#Ñ’XFrw+‘ía¦šnôS¥A"s Î]I…⬋SGšw j+±9ÆšàÞšeRdô!|2BÎð€A`ð)‰ZHq&ŸN æb»hb–@'èdŒ·äžp`&Ÿ€Ë‘2QŒÛÓ¼>Éi›y¬žÃâcŒÕÃy2’]iÀ§ãI-á§±™cÊÇÑFž,&| ΚŠÚ!ÂY!‹k¸ êµLÔ›„÷ËÚ)tŠsŒ6Ãp”¨Ì~談úk.+:c”¡šÀóQ„ǵýÃcó 2 ÀÃz'*²«›Ã2am]²¦ÈyÖ}½ww²~Æ;t\ãPH60}¡½ò÷ˆëòf5°VD+œæd‘[ʺ{)WžY —8ä:¦ÿ”q]c=c‰6E›|ÆK¤s´ª ¤s5^‘p½kîKD{Á5ÿ™~ìï^ý`3YlÊo½Šš/™gþi_øªZÓ:´ÀBcÓ—þîÖ^ö™FŒÿÔ}dšü*»,ïÎG½1Ò Ø“)hŒøHç…ÌDIÈ#‰Lz-W¢”Äk˜™aýó )ÁI¸'Hd€G$BPŸ“<˜}òR†}€³9ÏçìºO÷‹O&8°‡Z60)„fxrŠ B4B¯ûì_Z>B­—BÞq\€ÓâÎ1ÄtÅ¥¿Äe]øT7ùw›æ3ó Ôï#€.˜} è%2É´5àœ„x—þ 0 <š NÝ%x}–¹/¬ó_@JÚ7ï6^5Î| » Bë—A÷4dáVI1’ áž§}F­ÉJD·$2κÏÿÍ\ÓNã5—yÔn¼°EâÜõ"—–ñDÔ'Ð޲ê´0FTÉ^LøÕ?ѬMáêª|»@™¢Œ=!BUå»Ä¹ÀtDÂVÄ¡vZ&1=CX(¿Aý{EZEÆ¥¶çœGù ÎÄ6)ÒÎià!½çœƒhÕ>0OÀ9ë³MJa'j £:1dX"àšq®Îé²B€°>MND»y–E5níÂ>IØ&\ó—X÷¼BÚ$ÜÈ"s9ÁÑÍx$B³\aÀUÏs¶ý¾²Ve%ÿü_?Þ»·Y ./fˆ¦( “í„Ӕ܋Ix6+L3êzqÜþí›ÓEå ]/†ÓxKF»REt‹¸>äž•(‹›¼– 5И)ΡíŠ (8e4ä$ç”W&I”²t”s‹œ[àœë±]é5Ù宸ڕÖ{±FšÌ înžYmÅÒöiÿÃ¥:™ft7‡ÒzO [õà €ÕâvÊu]ðÏ(ŒYæ¢è.×Äd†L¥‰¤‚SAÍùÜãNÓ›“h«"ºs TxœG$Æ.31¨Ió¶A™^mpÝ1,ÓêªåÍ{[euÕò­òWÛÕ¥;+¹³•ìâ­ÕÜýê×Gí§7»ßµñÇ››ÿùîÖÏÏö~~±ÿÛëãßÞœüüòàÇg»¿¼>úïw~z¾{Þ'çð·× ¼±«¢ËëÃ?ÕRjøx>ÈÏÏw?$øIý½µ{B‡¿¾ÚÿÏ÷7!T;ö¼Ú?W›† œáýËÃ_x^ßüýÛ[  Àæ{`›WpèpΣŸž¾{ºÿã7û6TóöÑ&Ä|H Ä€|ÿxûÝÓ8öÑa{±H÷ÕYñí–²Ù”W+ÂÑ wo§y¶Y=ZήÖ‰Ðf#¶^–|rØþéñö›³Á··Ÿvïw^œ­¾¾·~k½|2Ìßßn>>ì<ØmÞݪl6ÅÃÅär™=ì+§«Ùõ:×L¢KùàF=(w6 GƒÄÍÕôj-À3¨°+un§¯'°Š‚-×…v–\ªD6{‰~•kèv‰éU¸^MhÙRšTÎ=8‘uÉQŸ"a’à•E,4‹tR°'x{5\lF‡}eÐSZu¡\o®çWù´Ob qÞÔ-“Ëm€µ+.·x V!x¾Ž¹BÐ|€Éx†M®§6òˆ@­*SÈ ‘£.I°K‚£\­¯d–•~Gj7øb.X̵2=èÉKh³Låà )àV³¬Â’ÄÙ1Ÿ$¸“ñ@6IJ<*°HLÂÎ ©EÛêy¶]á뢬-#£Z«}%« "k®‚ù$ZH¡YNûÆ:åp³@*œ ¼ž 5 a4.Ö„\ #Ü@,H=K'9/8Èÿ|YA«i\¤t oéV©dÌLnÛ%Ì}ÍmùR¤‚Iá iÁÔÈ¢:ÎfR¢%æ07Ô5\¶+ab^, Ó,wآ⠱ ;_­)Ñ\ÍúŠI„ÍB wQØxvm ñ‰6–’¾‚‚›0ø\Nv‹TÓ,>®S^FážSgŒ_ôLGMZtÁ3fcÎ(­û0+iŸ£ã k)ÊH#äüšs>`‰ÐC½PŠºâ!mIrE‹˜.sF ›Œb“ JG¹FPãÌt‘°]üS3žù*‚áÖ@Ük’zö C²HS¶5b90)ã“AËEÜø¹€MKølŸwk>§\ã<>ï3_si/ðèf¼†¯˜Æ?Ò\ÿˆrÊ”ŽA'H÷ Ÿå²uîÂ9¡°PåéÓ¨SwÕ0þ1b¸jùÜ8ñ1¸xÝ<ÝH‘À?2eͰg6+xx¿ŽEPã5Ò=…Z®ÐØ´ÒŒg-˜mÄ4ó±ï¼ß "p…iöÓóɫկ  î¼E]  Kqçj½¥´$2 KzoÐØ8GLEÃóù¸5'[Îf]Æœ ä j»(¦ VÔA]ø3 ¡±9›Ür›."–+ÝEéŽß>!OBÈ,bº<`ãÐ]Âl£Â-Á&ðiö3›æj½¡iF½U0ïvíEóìçˆù:ÄÜsBÈö¡S1`GXÓx!N®´Óí_Ë0…CpWÀà¿{÷Ì»ÌcÃê˜NÇð^]®åÙbŠRTb]QÆÅ&7‚^PüñˆDfb´­”"ë:'†­"m aÚ€wž&Œ"ãRŠ„í«²×1 Ts>VKÅ·uÂiàP;ó%æšÔ 8¦ITGc€Ü1ƒÙ§#¸‰ZÙÓX”rž…´àòqní0˜Aå:Ójze¾SŒ'Xg#MÑÈ<0¶§#nÒ5:Ä0ÞÊ­ŽÔÛÈ¢3<6·Þî7†%ö×GÏNú¸á’BhW+ìR–\)Ñ‹Ùà0Ú(Ñ{ ¾.º’øü­Ar·Áü¬—ÂgÃtKòìԸŤÿöJ€çîZ~½Â)”Å1³Œq¸^¦"iÉFý2í)¨  yÚ¦6GSVÈžq~½ÈyDÞuº,¹ êhStsQ~~wi³Íí-ŠÃ*ÑÉ#25ÓÊ{o”62ë‹ÒÆ’4l1;Cy¥Ãî ¥í¸µÈ¬Äú|k‘Ûpk­P¯ì‹QL ¹¸—Â"”©QbÖéZZéÆ6òçôµB(ŸôK¼\ËݵäóGý[û¹½å胓â݃ìýãìO¯W{o)i>Xëyw^±6Šèê€|¿¿½šÞÛ(°Ä ŠÒʬ)-:Hd‚Ãçó’Š€e©$ÉVžKò,´{AQÉÇŽY&\ú«à?‚ÀWÔ¯ m®äƒ…4å-¹`±\Œ°&’4"î™XIÈ(±6*áz1Ø©„*Y_¯J­/ÆV;4(¹)ÞUI¥$ ÀC¸§Ž1Ìt9h¿çZ W'éVˆéFÜÑHyx°œ!]Ø;…»&]Ω0ã`Y M7ä?ÔG9‡Ûv!t|¼WKŒsRd!Kå’d9KÅx(=XÝ•–Ø/Ó4>(3PÏ®Öøµ¿ß“ïo•Ÿ´íT€vN–’÷¶«§kÅ“ÕÂÁ ½ÕSζ«ß}½óÛ›[~wû7§ÿþîÖ_?ÞýëÇ;~sxæ•7G~`s ›ç`sòçÛU9ïóÛ·‡ ?>ÝúýÛ£__¼{ºõþÅÞ(zÿ|÷ÏïNþÁQ° g|RW)ýö¦*¯ANþ_x 1¼8úõ5\äHæýs¸ô„Àæð€1onBøòîêR‰m¥‰–¼ÑН·â«ØÎbz;Z-œ¬—†@9ª"{K¢ût9§NûóõίOvßÞ^¹»Q{q{åǯ÷–²ËUa¥&îö“;½Är-ÒHá§kž½žòä¨n5ÅnÚßÏ+Uæ` ,)·6òÍf“ád,PʱùT<©F‰+eÈJ–lU"íJ¤ß;«¥p&ŽÈ¼ ΖÉ¥¶;BѰ!ˆLåeo§Ä‚3»Ò‘ó²ÅµÀüÝPs IÐ>\hÉ¥[Π1Þ¨H‰7äRnÄqw_W8SJ´ç(#è >•YÙ%P:ÌyÃm½â¶]†Á=7*i´YðKô<鹚äuÃ&Ec“rÄÈ“ó€1`L¢´žôNDˆù¤àYsNö4ò¤D›¸ .#¹IdRá Î¡ýs Þ‘“x}JÄ™sn¶VÿpÍQèL‚·}è´Ì“spK€ Ö,ÓøN’¬­Cáñ…24S>Ÿ ã1S>ï±LZ/ ؇N¸5Ÿ„œ×)× ÒqÝ9÷×ì?³èÓãòA‡©‰zí¥œË%,Ñ ¾ £¡]W9t Ñ~Â"ãíg˜ù2j¼Ì 3 dâ’Î:æÕ©1ËuÔt·¸tŒSÿ°/|f_øÂcº¥Ì\ÀäÐ^³Ì]ÔŒ}âRƺ ´cšú„ñÎÎI™CM×]šK¬o!`g1 ¢¿ê·Yf>ì7ÒT=z°!DËh­+ï Ò@;À6¨eD ™pçFÀ5qžuŽ(eÀ7÷8`R^VS;_XÄ‘—]­3l`ŠÅ'¸à4\õ».RØíåÉ™:ª¾nÿŒuá3žÐE>h€3;u Óÿô˜®|¸VU—€ZÛ©»b›¿Hû4 –¹ nÃU¿}B?õ±má¢eþ‹´ãÒ_&=3\G¸¦×4ùÀdêQǬÇ2 ì Ô•¹´âž6 B‹‡›‚žx×aTŸ1Ô: ‘)Š!¼¦YØ‹»f÷ 0X3n䨲`Qm*âö™F3¼w£‚°¤)¯w‚ `ô˜nà¶q…LŒw®"ûv³Ÿ}s²øìd±qxæ>ÿááæ½õÒv]ì(ØÙ0³Uå¾=íÿpoµ-{ÿz{v{9S -7wWó5Áõõn}£Ì‚Gú6Ty•ˆ4+¢ß¬öœO²~…-ļµ±T‡Ìw• &0f%<´FÂŽqò*ù89Ú&°¶¸ØÓ­2‡ë™ƒÕäZ‡Û[‘×{‘nÉ¿ÞãjYŒôŽ·KÄβ¼Úëi伋5b¥I÷ËD3‡‚r²®¬µ¨^É{´®4sxQñž¿žäH½±ÖräR3š‹ûž)Ê¿ )\ÃÓV‰1ßÝ/>=«Ÿm'W¤[ÛÊÎ Ò)z qS¿†;T)ívéNh׉zãMµ€ï¬gÁU ù§“’3s)¼ðNðàS`³6paHc%L‹Þ”èeü:iD¢mPÐÀÑÞ¹€c¬S%…n»õH»Æ”sŒ‚dâîLÌÕ,†¤ˆ%à ñ¹ÔùªÍ%ÅUˆÛ×ÚìÁJ|½Ë· DAñDˆ¿kœôÎàî©zŽnX°ÒPÐTççn¿&…´9Ñ^R<>£ÍP´Ú]¦ë˜{¾IŸkÖi%<32ãÐ_B,×ê™`<¯3-!RÄU^9GF#6&¨åÀôf& eÚ\ ÖD9lUh[1ŠìõßœtïmÏÖ²gë™;[Å[•£ÕòñFmµ›tGËØ°©üúöþƒ£þ½ÝÖûgÇ¿¾8~qºôó7»}wöÓ³?8}ÿíþ¿8ùùÅÎßlüöíÁ¯¯€dŽS‡_©#³ æõþooÿúåîû×{¿¿QÿsÒÌœ3Òɇ?_p*µièÛCØTé赪@ +µÊ6'ç´süû·7AÿõÕño¯ÔÿY?=ÝýùÙÚôôêä§§û¾½ýÛ+`õ—ŠFê_­ãwߨ<¯î­þüìô[륵º¸ZÚ9 sÐV6kÛÃ\¯&v+ܰ!Ý?h?ØoŸ­—ﬗ¿9ì¼¹½òælùÍéÒÛ³•ǻͷw×{~üõI?/¸c”©]çdo%('ýE­§+5~·-ßY-½º5¨ÉÞF[­sm¸bäl³xºžß$˲'ɘêç»Ö›ÈjCØhK›­X'C¶ÓÁÕº8(sƒ*_KòùÚÊ1ÚÎÍ çÉÉx,â’WZBŽ“Ò€”VšÑí~ª Cá]oÇWR+ꕘýaj»¯ôŠaa[®ó‹•”îASjhõ¿X‰ÙZJ´ ¡í¡:“O‚wr”rT:’d,Ã"œ;*¢• _*0ë+…VMÌ¥ˆÅ–Á™9MzGDj.Éil‚ö«ýóc´1ÁÙà€ö¯¥ø¬ãÀ0nÃ5¿} jsÔך@Lc.ýˆiæ‚]sE­ Ó¨e̶pSçÖ·Ì}é1ŽøÔµ¶Çœ†Ër„A Y÷,j›ˆŒAϬCw €6„΄ [„t2„%%baŽ3®Vžé—ù^)’—°¤xoFÂêY¶–ad È^IÑRØå³MG‘´Çh€≠€‹R"šä=YÉ—‹ùS"Rּ쯦C`.š…H„0>q!IŸÞkŸzñئü^-²Ó3ÜIÈovy38¢°MH {#!{7ñ”#BÚ(Ÿ>J98Â"6 Ñ\s°YNRðêyhÌ»dÆÍV¯uœÅ ýªÐÈÒ ‚Ì‹¾FšpãREzv{£‹”d”²ûÀŸʧ 8§%ÒœáÜ•8^ã§ëå(®ßî*ßÞYÛnɽqw£\<» é Û©‹Úúh«ú×ëÓ½¦ü³’Wy×í•@N/áßoÅŠŒ­Co/gl”Ód†qö²´kþBØ5µ¿”s{1£ôØz'%ÕQÏ„“œ7Áz…%­Ú.0®(çR½6ΡÎ7ÈÙvW3O,ß;iì®$vWâKp#ïkäýIí®œ–ì‹õðb´¨£ÍäéN¶•ÇKqWV´w‹ÄfŸï±Fƹ?7ûb;OtŠ¡¥*SˆA‘7½ƒ üsÙšKø“1¯ÈyÂE]„wj­E¯5È£eñÁAnYX¬ú×:áÕ}÷¨ØoqAW̸2I{­ì+æ=,5Kâ“™„g}¨¬,ÅÊy"Béh°ÉŒiØ/ v¾UdzU>ÇÊ)"õ𴹘&ùp>æ C‚uœíÔÙ@Vñ6ÊT½L‰´AÒ…ÏÁ Ç#¦oP)'Ü­,ÖÍûëIW¯àßêqͬO$ç¡¢ök„o*Bpd&è£0 |Aï¬ß9VÇ¥j¼à7¯D‚º¤à zÁÔć@Zi¿˜“%¬Âg=Íôüùзç7Ç:ÿ¼¹Øáû{B`›ß_ÞýúòHí#­ÎìêjsÓ[•‚Þ??øY¨ùèý‹ƒwOw{uúþÅñOÏß«ý–áÎÔž<ê­ƒï¿ÚþQ%ŸÝçgÃggÃ''ý“õòJ/Í„¬¹¹»^Y_ÊvÊ|§9^/ßÞ‚ÿV-€îÍ" Åã)C5O'¬‡Ô†Ð´€'%X¡8Ÿ×¡ºo-'}­YËø¼¹S&zÕP„Pñठø²1Dí# ¾†äáIƒÇ|5ŒÍ)œ½–Ug ˆ:uN{Á™ûor@H†]Þg¿%€ (ÆX AŒ±fcÞxÄÊ(×Ö ÃgŒ9ɹX §y+ЈœËp¦FÒÛÉú´.#˜òQ+Þ?Q–IZ'fÒ´¦$˜³Œ®À˼)Œ5$s6j”¨18-’3”÷†ßv©sq€yž1Ây#±³¾ypºš‹®cpyá‚nêÓé‘OÆ>1Ì|9w㓌„;u#S×>™¸ôÉÂø§º‰/=&µk„]{Ý<û¥fü»öb”2³¸Öïb±k.’ž‘4ñA#Ä» Wqçd"âª]‡­‘ä¼vÕ2d’wÿã~ ÷ÛÇR=„ñkY\Gz€B§%Zçw^çCšHp±\åI½:Ó£ád>Oê˜Àƒc8–,¥”'„ŽÜ×|Š'çþ)Ž8Ÿ™wµÞ \3 ¦óY'¼æ±€sÖe¸a™UWöš'<†1xFëÜÀž¢1Näòaæ=P|Ö1Ü9Ïb×]D­£iÎñ˜nÀI¼– PhL UX™ á:`#Q]3øSPÝ÷ÊÂöb˜§’ xÀDIagJðñ„#lqÆüà1‡}†R‚ÊFñ8ë–h0”b@|ΉG\´_WJ›ªm¯¤)‘²±S4laúi…êÛ¤Á½:Ÿgô Oç0‚¸-˜{€P'ʸ T1nh(•n¿s6J9Ó"€™Ú’#†À ³2~# d‘Ï6EyuJ ûô Þ»ØÒ¢õë‹2^K…º…Hšó(”ýÁaX“Jñ0º ×ýŽÉwÁï˜àq=Tµ‹e./xúE¶(zb„áÙÍ¥A>ZL òt" »»V|¸UYL€=wVò§Kéå<µ^bŸu|{V 4„ wŠÐA‚Õ"ýü¸_‹ù‰ ïÓ$(Ëý½æííJ;OŠAM3\kE#~4URd9A%#(å3ˆ@n¬KŒ¸£¼G†;Ý1°N[%ÜYIw*t¿Foâêä®uºY$à«‹±Ö„`oˆrmñ~ÊÉŽRÊ[IzÕž3QûRÚèFu¢Wôí.ŠUÅUˆÚ×R_ßêì Q]\o ~”±ð”‰§-®c)K¥ÀtëÜÝÂɪ|s-¾Ö¤J²e£Ç|}ÖØY‘5­p·F´kAÕÄDC>ínT…´—ôOfdçR‡Ï*‘1ÄEûR[Ì%|qÁ%2ÖB*çÝa\—û‹©`„2EB¦cKpN „N¹³ß¬eƒqÞ°:€šRQ'ØÃRÒ—:+)_¯^·ê‰fÚ+S b`ºw–®´ßÆŒß9Básp- ̲w!ˆj$Â䵎ùÝSi ù<–ëêÍ$pÒ§ÁS”_Gû4ÈmðØçœ¦q¿kNfÝiQ5«¹–Æ·àdÆÂœl³!µÀrjÇQÁÁ‘…aŒò.ð„‰òÌÂG•æÍ4q¶UzxØxr³ýÍ­þñF%Î{û0úQ×»ðÆ ½²x´ZÞ—wVzvðûÛÓŸßž¼ûöèÝëÃ_¿;}ÿæèÝ‹½ß^ýü|ï¯wwcÞ¿<ø÷÷7ÿîæ¯oûáæ¿žïüñöæ¿ßÞüv½Ø‡d¼9ùóû[¿ ˆ² ‰A˜ßÕ¾=ÇpßÏ[u~}uüx>Ðl‚®¶ö¨ÿ³T åßoon~ wß@øëË›¿¿¾õë«›ïŸÃåŽ!ò_Oö "Õ?_/Ž¿ûzowµ˜UÝFl})×,²¿ÑO®—óöÚßœ,~}Ø}vÜ{gõ»»k/o.½8YúþÞÆË›ƒ7wÖ¿{¸uo·±ÖˆÖRaMØê'6» €ÈN?¹Ñ–7Ú±{»õwöêý|¨‘ô/gö«u”£åôÙféöVi)±T ÷òäNW–" íçéý~jPŽœÿ2Kot”½Ang)³·\4åõ~zØŽgcØJWÙZ;ÉôóT9Žã¾’ìîÔøõnîdØ—*ÜF7¾Þ–[ÙÐf7®ÆÔÄZŽŠóHZö/5å•üö0»Ö‹¯ÿ_®ÞƒËm#YØþ£wïõzeå0y†9g @ H‚9ç œ<#4 –œ×¶lY–“,9¬wßÂè~á¼çÔéÓ(4šˆ®zªÑèH[}qØàjÀ'¼Vaùl\ѼFä¬RáæóÆx Íwj½–¸9ÖgýcÓ®4éˆ{ýt^Ùн¡KÁ’6˜Í¾4l²-ƒ¬pMB!ªæÐ¢J´ê‚¡ÓF‘)YAHð|Dbj..óán-3lòã6?i m#]+à­2 q‡*„`³]"àg,¤×øà¬-À…w ²œ‹±˜•ŠCà¹(Ž“M}Ò EÂR|tb9¼¡çE9¨åLÚ’N® ‘;A÷å n­jhŽñA« »Þû/ˇ&xdÆ)R€~*æ*¨M%b(¡^ËE0öJ©iɪšnP…'ì?<.ƸR”"6¹‹Îê¨çš&AƒønÌJ¼¢& <_ƒH'`¿Ä¥lpøk(–%¡¯× I•óÉŒ[强¬åcý QÉ…†¢À¹™ø¢LZͯ8oq¨”­¡„äôº!ºuÖ–]¥"×RþЬ½&z«‚»%zZ´&x[¹à¸ï(¡N>¤PëºàVÑž‘(eÝxèZÈöß<¶ÆÄ—à¾åHxãÕ\Lç‚ÎÅÿ""KDt%ìºv^…Ó3WÀLÚ€[† ¾$ÆCŽkÖ;³.ü7⹓c‚L¹rí¿H0_Þ[!ÇU‘ô¤ckK”·®áæËÇ´+æ¾æ x*I7ðLž ^ä= $²®ña¬ql-⺕ ],»i½ÉàÂÚÍÿÔ!ã ‰àužØHÇ—\k‹¸¯…]W6á6r)«Ê{ù´IDÀ®YÊb(A‰¶ÒèRžs i ¹ó\¸.;–ÿÅ$l`-wþæXzõ¯ø-W‹ï‡·÷"¤AÛ ¨ëZw­üò!ûu@#¿å ”¿àŸK©È œ€wã฽—×oÿZs,½t\‡xÓµö^:é!â.9\É„/úv"¥j(ÉŠ’lêd57th d”L°]Îêà‘u µgR d/ ´^Ã…{ÂOÄÁÝØ<Ök~ç-àðï° 2 îÁ.ºƒ¨¤‹' 1ßp¥ýXxH†A]7YÊD2癊l$ƒ«žµËqïܺæ/¾ø.å0]Œ§k*².¦}. ‡ÚQ÷íª˜P\&.¦%ñ¬¿ïY}Œ®‰)ç¤Î ¨Ž¬VÅh]BžîÕ%Ôr6)Œt¢›Cm–Ž»bSCúéAó É?W&:þùQûþHÔ9éåʤ{^ežl—Ë”çѬD6¼_Hïv6¶‘p^¯åѲÙâÕŒ§,†7[‚LyYÔ ^µ"cåFà ¡ƒ éeHÏ1VT“eaø@­„SØ:@]fÈñÐÉõn•4³ß5Ë„ž ukDMÓÉ•\ÆUÓâ4•À6æM=º7æÏvó[M¢§G*YÏþ€}0×vº™²Øì°›jA ±¤CɆYÊÆÖÈ”KÌø¶{L]öŽk‰³Üá„ß7?õÊŽøÓ³Úæ s<ÏïmŠºâ2‘wôšf†bþ+L{\¿Ni¹`]*ÆÀb+À6P?ެÇ …\¼^¦HÌŠÇVù´«W·Â*Ìþ(V+_h¡ð –´%÷ÙÔF]‹óø†DÙÖÕÐ^ÕR59À wäv·Ÿ4Éi›)æBhèvØ{CVñø™t¤QGøóÙ¸ÂGȤ-iŽ+³ò¤[ËŪ"Cx€Éáù—‘à‰û“qWÄ¿ñ-%#ëXd•B7JR~ZLÛÊR¸SJÖÕ¸j®iç +_#e*¹Ñ*¥•£}`UòLPÖœî;¶Ú-¥¶»‚É<Ç%¥R@4aÆLàA ab@›ÁA]<šU6Û¹Ý^þ|¿ùù£éﯞ¼ùñÉﯟ½z~÷×WþýúÙ¯@ ßÏÜÿ׫‡¯¿>ùýûo_žýúâôÍ÷g¿š£}ÎMùþÀÌ›ç'fÑÏOo€vÞ~{×äŸï€\@Ñèÿ|y~A2.Þd™böí|÷àׯxÎLÙÿc’Ï¿¾?ÿ÷«G½zòçËG¿¿0‘2ÿúþ1dÞ½ÛzýåÝÍQÍg<ÏŸl?;î|z<í䶆…G'ƒý©Ñ)3óvw»z0ÖêáH;êæµÏïöŸŸO¿~0þÜ\–¢õúÓ£7?;ê>0ùì¤ÿp» |Ò+ý29ïåvJ¯BÕòñº‚œmêŸÞíB:oóƒÜ¬Îì÷ÄgÇ­ãq4‡C”## úÇûµy[¨‘®†  ÝnîÞv¢?xöfiÚÎð@øfä“íJf³'N‹ã: §[Nƒ´‹)ÈÏtxP'M®"#¥lÊçèBhÖä&Ú¤)ð´+lEO¤x­HÌzP³0n±Ó®0l z¦]çz]¹ÑÕ)ˆq’öKÔáAg2*LGú ››µQ[æé×Y#v zPg{UrРMfÒáw'êþfiÚ˵ªt­Dj *0æWZªœlÖ„j‰-ë Ï#TªJ|véùdYM L»ÙA#c~ko¾.ÄÀŒt+t·JM„šœ9®Û€Ö$ÀMÎ$-€=Àü†ŒÈŒ·(F".ðŒÛ=@;®¡ÆùäZßÀ[%´” ¶+˜@Y1h’Ä»M›MC d|Œ¯ Áëà'm¦¡ÇeÆÿ†GTÞO£kGLÚl³ˆ‚ÉÊóî°ëïu˜ÑŰ&„Ö—Á¬€:À9P›Âú+ùø;ø]÷Õ$à  'ã«@;u y"¶’FV™ä†v1 †ùVZAmpù2ãá ›’q—¤P%éWRõ|Äœï=¶´SWBã’+Lô¶Î:Ää’€Þa‘›À9#Í¥V³ÉE=c›·Èkæ%ÆQ}ýB¼Â{5Ú®26&~ÇÈùóŒ=ƒ. øZŽ´æHÜ´z>¶ÙæÚÕ.$ÊôÝÔ2Þ<ãk7JÙ´“OÙáȳþa-ŠÑN™ª©˜ÂøLÂŽë«×ÿ–£üºIøLVIÚ€ cȉ‹Ù†7°ÐbŽö\ÌD´5€"¢«æaßm £¸÷6”?HÆÖ€%à@ÐmWJ"­˜ÞAüW³” ,p2t;è¸lYø[Ì{,dB†B×2fÝFM‹•d@ µ²ûŸŠÞ†ØS¢| ÷ Ô?°^ñ¬ýùü÷˜û6^·ÞùoÛ÷‚Ö1ׂÙÃc½²ßàA<‹qï"øwHÃŽëÕ Í$Ÿ í*Gèâ½I:ÂΛk·€£n›‹¡Ûol×cÞ;ÛÕdÌN¢^.`q/Ox/ÖF÷øIiϤ!kü¨!‚9*IXž¶K\M¥Ò1›@øsT˜N85ÆI?€„Ì„ŠÚ1˜~•›´snv•^•­i)q+—càb!Ô S˜ ¯‡.VQG+XÔ‚ÅíxÂI&]bp¯@…4Íq±X`œK<´,ô~ˆ„2P@b"á˦TÜA"¶<S9D |pn†œâS>@2ó ™c˜ÃhhÁÝ1ÿƒ9™„]"|2éOxî}‹˜çvhã^,¶s^EÜשè^)²!³“¡E Ç{OOºu!üã³ùƒI`æþXÛ«1 €4Ÿì7ÛÙŸ?Ú3hïHÃ>Ü6@ @µl–Éó©\to˜‡]÷'ú¨LU„ؤÂÒáU&¶V#G ¨UZk ZÏcÀŸ°,€iÊ%$r6 pAYŠiJB•‘¼÷¤b>¡ËH!ˉõÓ¸=ÇF 9,/"96š~“-åãlʦ a·s);DO“N¦ª†u|ˆ¥OÝp§cîÑ<¿ÙHÍ©y7óðÐØåúu²QÆuá .I8ñø ‡­Øb·}|P||Xš¶ˆ†î”ãP›ÄØrœÃЭ*VÌb-›±5Jñnk¶=:\dœãbRv ·‰¬_ÎFÑØz4¸ÄÑ~!,«X†pR¨eÒÊžÍk ëÔV?×(áíL"‹ºJ&Vt)4¬S9Òa˜/ô݆îWðIƒjéˆLÙuÁSS"Õ|¤¡' ò¥±õxøZˆïí aóÑ’³ñ¢‚ l€Â$nÏ2*ådwsI'•27{ ̇c¾bGc6ƒ³AÓFƒ·°à­<ãÐjjH[GUȧ(4Á§e¨‚ùÝ*y.VŒRŽ‚Væò) a p˜¥UÀZ%‚ÆYbmÇ]±¸ AíI܃¢MÆûÍÜþ´²Õ×¶úùùP»»Wÿá›ûß{ÿÓöF]éh·úö‡§üôì§ç÷_sÿïÿüÕéÛþüáÑ›ïî¿þîÞÛWçþôøÇìäùþÁï/ïÿöâì¯ýûÇGo¾¹ûËWǯŸŸ¼ùæ°çí‹{½þúø—ç¿¿|õ¿ò?ÿóûó¿~x ò¯Aþí·g¯ŸŸB%o_œ½5»t¼y~ÿù×3ÿÍý_ŸŸýöâ2?~qüÓ'ßrp»Ò/¦»EroTØfå½Qq\ÜF5nÚÌn·¥Nn»-rÜß6>?=¸ùåýɧǽç÷§Ÿ÷ŸŸM~x²ûòáö7g“÷ZÎëÏö[{Ý\UBŒÒ* Ís¾YƒÝnr'cõ“Óî÷‡÷·Š51\#g›Å»ÓÂ~OÚiq»á°ŸƒÍÛå­×VÑíÝ2¶:ÒÞHÛŸ·ùZ¹pQF›%þ…ÝQ~¢Í:â Æ€ë_å|¨Œ/€ò‰­®´ÕËlu³G›ÅI;[à©r†žV„h¥€ðl åfïVɦAµd·í´Ä²Áäóx.‡r|´\f¶¶*%ÌçÐݭʰ+÷ÂÖPkI‰ ƒU5 *Ù™ä·FJ·ž©êx­D4 ªZ"ëc)@šœ€¨9¼^Œ"+‹Ç!Å"S­ò²Œæs‰¢ŠiR¬¢&áYÝæÀ8tŒô¨Å çjô¤›ßZWƒ*Õ-ÀuR*^ÐXßÞP–Hg–°—Äp2p ÊlwÅN /e,ºÒ)¢U5*Rö¼àe K>뇖ö]C#wLàA×s8#K:cÞ˩譒¹fD¨YLÖÍ%®–ˆØRÄ}¥ªÆG ¦Uå£rÆ©òînb>‘Y_Žñª|0ÏA‹ Aš%]BÚ)g|Ps!nSg’4tô° …%ÚS–(ÌSÕЂ†’ "åÃå!_ÊEXt­Àûû•´‘ Uä0€EÁWÍ… 1P¾ÎÆE|UWÎ9åÔJ>½šEïhÔF‘±¡®÷¤äÒñïjÑ<±. +"¶Z`œÕl@%írÚõÐÈ¢”¶â¡›dôN§˜ì–1õH°KŽ.&¼×r¤]$lëÝ憵47Ô ß²-üoýt!@DàHG—ؤ%GºóŒOÛž´eS.C6ßÑø,WâÞ;Xhˆ‚4—Î4ßlØÿñ]Ï]̉x¯3¨%`»°]A¼·¨¸¯ä€¥Šºox×ÿ)@NÌäp2î4Ó”Œ/«‚OHÛ±ðbÔ{Ó½þ~*ºv^»¯ iÜ·BMðC°É +\jñ^ÌÛã¹’Š.¼ááE™ñg’6 –°óF*¼NÆ,Dt#`»f_ü»ÏÊ[€+Þõ+~‹É<‹ÅÍã>³+ø'î]¢;ƒ:é¸=`¿°_öÛ.Ñu8„ż€+Û ,´rÜJDlæ~H#vàð­E1¾ÓSÆu~T:%ƱF씹rßê{Ѻ“òEà(0FŽÉÔ´´öIt°]¢»F¦^ $Êo 5 :€Ðƒí׎0{x$Æ„™bõ;o%£–”‰1.NÆ»yž ŠLÎÀl\·v‚nðM&ê`1k"¼™|6Q0 \téxoj*)3‘TšOžIT8 Îp.l>…Ü XÔ*RAÀ¤lÊË&ÌîÙë*ß<™?Ø©~|ÚA䂬žïVëy4é¿ón*&ºšòß–SÎÇóÚ“ym á?}´{Ò—÷Ü“mãÙNå)°ÍNø§Á…¾º7èÉñý&ÿíùäÓƒ”ie#6K_wOûòDO5øÐÃYiX"ƒkÿx°S»¿]’6ñk,ühKê¹D‹¨lHe!ðÈ™pAAå\˜$'FE!œ¡Ü锥ܳ‘¶=-6«™|6*ÐþBàˤCÙL\ä– ä¸p½LÊ€:„kÒ•[S’­2YCe6©£Mé|WùøXÿì®ñõ£öÁ}rXzzlÌÚæˆè‡Çõ‡§­A‹ÍñŽrgH·Äy‹‚wZOA™¯Ÿ¾|Ü{°¯oõÀì“ýZª¡#Åœ¿¢E»5Bâ\,ià.Ï¹Ž¶ó3ew"MÚh nàÉqñæ„H^Ž›Ý3‘‹2¤·UcÁp„œÈɦQ•Ѫ’€Xøî~Ub½Hè–Èy»5rw¢njÝRjÚáÚåT¿J´KÉF!Þ( „frD¢ mnäY¯*ÒEáVÝH%Ö)Üóˆ*¡²Å7àÖIk:i xo„<7‘ð ‘°Ch Ù@"¶DÜ‹X¢¡Õdt€'KºEÊÑ(°Σó^È· 0Ε|´ª!½Ù©à>À–„pIB°Ð2Su 48°a×Up%1J&$áIÞPd5š°%pй¡µDÌÊÑáQ;¸YÛ—ÀÓ5 js”?>hOÇEIˆ– Äû£OŸì|õñþ_¯?ýÏÛ/~{õä߯?ùÏ›ÿüù)ÈÛW_wöÛK €“Óß^Ü3ûp.°Pç7rî½ùö.d@» sÁ?goL=äÍ6aïo/îÿûÇ'ÿz´óä÷ï¼Û ™Ÿ¿<ýÙü,ëîë¯îö¼5ûvÎA ÿçËǯ¿>{õÙÑŸùpv0T»%rTØìÈ£?kI§[UžA•Ýí«»ƒüN[Új À<ûƒüýêgg£—í¿x²óýÓÝïmÿôì€çÅýÙOOv|4~ošwîÖæ}¨£mÝ Ù/ýÖÈÅ@öºâW'OökGCeZ¥¦Uz·“:ÈýÜéÄÌÌÛÙ è»Ò~_žÖ¹I=à$Û%rPc6YOž´¥aK,å±IG:Ú6v/fÅÔ36íd·û¹n…7¹½‰6æáYÝ1-µKÄN_š¶³½*ch˜*Æõ<Öoe÷·½ÍâÑv h§]#ëåt ªk“‰^­²¥2]*Ñr-hD¿+òx©@Ì7V……& €§¢2Éóqa{¬Î†Ê -@ ²s-¨ÓÌÖÀ WXEBU+ä‰j™ÕUŠ¥#•*•L¦’¥„Q„Ÿ&›¥4\œÉ´# jô¨ÉnöÄF1U’P¸¸þ£YápªN[ MžñB:¬Ñ* Ö TA"'[:Ï´Å&}W™Är¯ŒA€á<Ø.m«Ñ, ÄF§,©Ä ƒY. 1^ðŒa®(šçÜyÎ.’Å6ü¶÷“¡Û*çïW)8ŸFm—±¢¸˜ÞÉ$7 %® !š–„<œg%ŸèV) ™‹·ZQ¨ÚU³FÜ×dsrl€Gfý :"ÕZûßùRHÂ9Ø¡€@:'$ÒQË#í"ÊbkæºZœG¦EÎ[`]9bCJ­×åà´AŒ*XG‹ÔW…µeBWDt¡)y«¼«”qÔDo]ô«i‹!øZyx#EÖËĉàM*¶ˆú®QÈr*t âµY+£²nY*‹A0\楄F–Éè"¾S`UÈØ‚HXtÁ—ŽÜ¡âKp{Å´µ(ÏUóÓ-וˆã²ßò÷lÊÑ7h1í{CŽTÜæÝøÀ½ú~ž q¸Ã»ñw…õr¬UÄÖ“ŠÜöÛÞ#à”ÌIù–ñð2ájõ[?9®nÜ~2€I>Ë%4°˜ð/8–ßC   Ø/s¦‘ G&i :®&CK1ï-ÛÒ{Ž•÷XÌ‘¾øö ,$^àÓ6ø‰°ë⻦€‡KYÁî‘Èj‡ó\f“wÇÜw>8™ýFÄy1¯âfØq+ê^€°6ÏâÅ‹­› ÿ2äÏBYOàJ=—×o½g]xˆÚÀÑg’^¿õ&ˆwã: °DP¤ÃXxCa#½ ·ÕË÷ ¦o0ãº0iJ%ºèá‰ÉLŒA=æ:ïÐ gžE@ °¡ò±q+'3!.åiÒÍ"U–á q–rhS'5¾W峤 ­2IgÓ`y2óƒ7qí½èùt¥˜ž ™tEýË< ©¸ý]I±ùpdÐì¸àãxÄ‚x—"®;!ç-ÀžLÒ-~‰^]";ðÆÇáôXÌùR£07 ¨ƒ¹s 'µ$¹;PioÄv¹UÀ5󕇿’KR\¦ý€3=¬e+ùfñ°oV椈œ‹ù\‰®È"Ò¬qe=- GYÒŸ—°¢š)ª¬œÅ„L´ cy)nŠˆô›b¯‘Uø—öИM•Bƒ&ÅkCùp_}²§žÏåq%ñ`®>Ú/Bs®åûCñéýÎùqmwš‡P(E—B/>Ý:žæ@Îv´ÍÕ-%¦múx+_’CƒzºV@šÖ20‰Äb§]¶šÎr§;ÚÞ$µ7•Ëy„%튒¸€š‹WÀäÖ¸fC¨¬Q¢uuÄN…žu¥»;Up‘æw1à¡¶tU íÔÊxÛH••¨¹¬„*B@åý`‹RH$*ç5-p.R’ªÔŰH{tI9²ŒObƒxÌ’Fí%+æ“ÉèJ<¼HaV$´)Z †¬F¼wHÌIÄí‰èF4¸†"vu¸›LÚiÔª°~°M-˜ùIa•ig>±U¸k¤t1XÉGíà|à4òœ¿(ÅÀ–¶ ª^HÉL ¬ dÜÂ`¶v™lt­Dæa¨=ž°¦H$aEÎbGbö4æwÕ»ûÝqqÔ–º5®Qa‡}­ÛÍ7›b«!öÚ¹A[¾{Ðùý—Ï~üæü‹§ó¯>Úyùåé¿~ùè·žüüíÙ€=ßÜýé«ã7@5¯š?ßÜ…ôÏÃ&àÈïߟC±‹Íó_¿=yýí=ó}Ù‹³·ßÝ7ËÀ±/L=”yWŽ‚J~ûò~ÿþñŸ¯>ü×OÿúéÙ¿þè?¿|üó×÷@ »~úêîëoîC±—Ÿ~r6ž÷ò;]e»#oµ¥ýaát³r23îm׎g¥½ºÓ•·»¹Í¶8kfçýüÝ™ñx¯ùõùìë³É'ƒO÷»_~ùpÿ׿>~w¾õêÃÝ—Ow¿x09Ù*ff4ØŽ7s1ƒõ×ÅÈýÍâgƒ>ÙÛª1m%>1ȳ™~:V‡ ìÚïIÀ<Ç£üÙVé@h”߇ÍYñh¦m–wGÚé¼¶3R÷¦úþ¬T)¤š ùÝ©v´UµùŠŠBz¸UšOÔݱz´S5¹–‘ÞæwÆùAžq>R€|ºU¦¦¥<^TS½¶x¼W?šWŽæÆÞL› ¥aWö¤­òþ~³Ra@Zl­’iÖ¹é°P3˜aOväZ‘l”èF‰ªˆ^CØö7QW‚cg#u<Ì·ë\»! ºr¿#·êB³ž•Ež”ª¤ŠJ×h¬w>¥ª„”µÔ¨›´„~#³;ΛƒÀ{bÏ Þ­œe(ˆD»«jbÖæº%l\§÷†Ò BB¾U@{Fª"Gá±×yÿ´É kÔù~¥žGB",£:1d ¢ŸI®2Ø:mÈ‚ŸÄ,Ð qd%[â»Ì™ŸM¥ãËlÍì½á¼rÆ%ÑNp¦$²VSÑj­©‰^…¬äcæH³¿È&3æ70@;pó›%¢¡›“½#h˜ïô}>Á²Œ˜>ÅzUôÐA·ÞõçdIW*ºŒG–L9 ÈI£ëùŒ§$›Z\ü'ë©+‘’àoÈáf>Rȸšùð¬‘îÑ««5ÁÙ]Bì»^¢-À<µ¬§@Y Σ’ÖT%G+bPg}Llõ\Sh[Ê$ÖyÌ"¦ì 5žôßdâ+,ºNÆ–Þ«: øê¿®fÜbÒì ÞlªpoSÚ,$ž+<¶Î£ë¨ï:깞ðÞ c+DxQ&\æð­_È .=›À‚+Aû5‘ô@¨%3¾²)'Üd"¶Àâk\ÊüäJ œqßÏú?ïílÚTТñ@LS'Ì·]YºoíȌߺøw,„s3ƒÙ©„%ä¼NÄ6àW°ðZÔs+á_Œ¸oÈ0$ æ»ñÌ º&vxœbžk#”†×x N2fápWÜ»ðnÆcsV=÷HñÐâ]ˆy̼¹–Ådó3´KÎåK¾kö…÷KïûmWCÎa×͈û¶ßzݳ~Åg1S:áv._q­\E<«$æ#n.HV%:Ø)e¦íܨÆRb\ÏÎ:rU1×fñYnøm7ñ°…ˆÙ”LlÜTÚ%6G‡e&<êE ¤éVX8 à¨)íÔ DM# ¿=ÔkZšÅ¶}"fRˆš92Œ€gñA+qhb2éV„¸ÈDT1 »"¾e,frN2j¡qÄÆ!Œ”Iùs¬2üe6Ä·Ì . œ˜g€öJtÎP¢Báã™°"¢‰ðº9ŸOÒVxíµÿõ݇s0¹=Ù·ü~>í<èKzÆóõãÙÝY)îºNEVÊBäl»òÉiïÃýæ¼É}~·¯$m›eòl”«É£¶ðÙaë´'äüó´ÿáNe·nvûl–©”ójSˆíL‹Èi_y¶[Ÿ•ÒÀ<ŸwNÆ*¼ 4µßWæ)ᾑM9¹¤]$Ü:)p á^…U¹<± «Éq9³!óˆÙ°(DD!Ì1As(R¢Qå:MIW Šðæ¥CÆ$x¬\`Æý‚®¦j%¦¢Ó%•hU8]Nr¨"D›er{¬ˆ´½ÀÚ¿<9h?2Wíýpòüáäd¨4Åè¬JïÔÙi…úú|òp»|:ÌõeÐö<Ú6€ˆzZBžOº'“Âfƒë—ˆ]ˆVvkOOÇ[Æ|¨mwwëgíÓÝú°-ÌúÒÖð»4lqy!XΛŸoƒr{¤l•’Œè¹ØÞ¬0«àgÇ-þt^>Û¯O»R¯ÎÍš¡“lÔÁvywSxÚ>Ø)mÕÍÒ 'ïlUºmÉ(‘¥QÈ'kefsRÚ9Þo;Ò°-;20O§ÆÍáŽõ]¸]›åÙ¸0©¦ Ê(A¥Y)1ƒh¤,‡HÙ<*å"[ȺJ–‹t>—ÔUN©YJWòñn…Øê ³6[õ<2i²Ð¦XlCã|}#UÊzeü`¢R8ϸ;%l³ÍB ¿DÚŸ7jJPüuulæwz¸Ô4²,2.hYãI%Ö0d„IÙ€vØð1ß5‘²—å04™B6Ø*¥ê…dIŠí0I €b§L–Í…ƒ*ïÛŠU5VÊE5!¬@¬Äß½ÃjS¥\Ìœ[Çå‹ÏͲ¤»¬ÄÁhöTÕd½€Aa0À?¬ó9Ú/ âàñ×`¯„(kÞKXx™„»‡›¨“,F=·ßb2´š,GÜ 1ïPG±ä¹p*¶êZ{?4 /„W!Ãb€¬¾€í2œTˆå·\IEÖÞY:'c. ʰó&À¤pÀ? bÚ®ÚßÚIøVÂöÛ17ðÕí˜ÉE ~\Å5ÏÆUÇò%×êå¨{)â\Jm ¿E`b¨Pص@£ÎŠ’7ÄQßêȃ*_ͧz¾¡ÓaŽfq?zø”?ZïUÅÝ‘9¶ô•| `¦CÚ¯Ay@q\ã½*›†‚iB¬Sá b‚Ky.šBl\ÚŸã šw„“B]XÜö.¦âv  4bÎ ®ŸÐ¸7ê_I£®,&“.ž ©"J¢.ÚdW`xøGœð·ädÓƒø'±ÀyBžŒ;€Ö€èx2˜ÆÜb& ´,ÔPÓEy¼ßùùó»]åx ²ÑeÄöÁç÷z,²r§ô×nµ²ttUÂí³ûd¿ùéIoVeþýòÑÉ@–âkíÕ§zêé¼òÃãÍgóêGû{Cµš ä“¶7Ÿ¿|¼5€&Ƈú éÝAþÁDÿh¯q6Ö&:ñhËx8¯‰¸=OzkRül»–­(”¿œE²¸‹O:Ds9^ã£zvÚÊ[ù` q‡¯”SÚ£H±z…ÑT¢ye™†:X&\*ÐR-E"EX4—ÅG½B¯-ƒõK%,…8›òT /ÆE6PR2ëeK›-úÁ®¾7æ=n\'öFÙq# Tso[Ýî±ã9ª§ÛÅÄvxXÙés'[ù£™|b®ÃEîÅ{»ÅÏ Æm¦ª!9Æ•Á×EÚ±5Ȟ̋GÛÚÙni«Ë¾›ivP'›¥d¿ÎhR¸[gyÆòÝbiŸ®¥¢á•DÂWD§ÝšàÑ–µª$ÁýÍÚbŽ1— œ¤V´¹f¯›ó¶1%1$¦ …P´(EdÖ—Š­Ô/æRS¸Û 9¤Q¦jÅtQAe¢?<î¿#’^µ@ô…ï˜ îð¾~=  2¨S‡3 (ePgvJSOOÚ¢@ûâá¥dtõbF©;Yl¬Ge}ºÔx]7.Ì,K8T!Ò(¥ªZb¨‚©ëÉ n@RåP&ÇúEÚ[Q±F™ÔeTdñÀ¢Ì†ò|T¢< ‡W½¾…dÒ•Ny©”¿¨¤ûuikPÜŸV§¥WãÇ]e:*÷ººQZ ¹×ÑÚMYÍ!©·û;ÓýîÝÃîþfõÛ/NÿüåÓß~xöÓ×÷þøþñ_?=ûíåÃ×ߢ<úãÕc`Ø|ûÝ9d~ÿþèß¼z9{ûòÁfîÿúâ 2°ùÿ*ÿÿzP‚üúíùÏ_Ÿ˜œóâ6ß~€ôáïß?†Mß^> øñË»þðùéÏÿ¼òí³½Çí­–TW’ê•™YS<÷Ç:¢qKÜê)'[•IÝëʇõ°—¿?-q:ìùêÞäù½é×gÓŸüþÕýŸ>>üòlúùéè‹{ÃÛµ¿=Ÿ¾úh÷Å£Ù—g£O[gS}·Á?Ü6žìT?9j?˜·ªÌQWÚªevÛÂñH=ß©˜Ÿ4Ø­?ïJGSýd«¼éveÚ‘u޶+N:ÒÉnõëO÷!-]¼XÑÄp·ÆœÌ+ó‰6í )Çó df= jèUéÃYép³¤eúMžj ½ëäQØ€œñÃæ”q´Çœ/=ãÿßWl˜Ý–«Ê1eQ—BÚË‚_g=#ÙÊGôŒ³¯#{Ý̬–èȸ‘ 3é*Á ëàIû/ãîR0¼•M®ëŒ¯I)»˜²aÛ©Ð’sñ]mp³’Ñz×/©¬W¦]ÉÀ-]Ç·œK—ðà­Tð¶L:+b8‹[‰ÐB:´È£Ö¸ë„ä¨çfpãÄyƒKØâ®[¸1æ¼sÝv^ôø-W×n\ài—)9W/ÄŠ¤Ý\BZ h‚ÜÊ’6@J!íIE×lˆèZÜ¿R–QCFù”+›öp¸ô>ËP€Amiþ‹ “´×öë!Ç­ˆk!µ:W>X¾vÉrç’kíYƒ Z¯õ' ¥„ñÞò[/3(ü¿žwJ¯å¿õ*‡»¢®[qïˆßz´°_EËAýKTÜrÜ\¿u 0Æ\"°÷.¬·¼ë×ËXnÃÏ]F¼Ë®Õ¼–kˆw)h7ÏR×ê¿õ¶}éŠ{íF:é#âzÄ.R@ H·œiÒã†0ª í3m+ƨrˆÌDT.‘Fœ<á—™hIÂó,t¡d¢,îÎsÑR.Ù1ØV‘¼És‰ù jB)‡Ò¨]bB,îÉ`h' RRÊDØ´‹ÙfJù4ÞaR¾‹±:ÎX`5“t¿cždÔ ™ á—Ø(Å‘Á,¡1ÀŒdö/E²é P w0¨›ˆZaPl éÀŧëq‘ 1¸G CŠ7¿2FlÛÝ<ØØÝ®òåùìx¨\hX$dÜþÓ‡3pÄÏ^O>iTx© ­ƒ ¶t§Éƒaüç½þ‡»U-å¸?ÑîäÏÛ/Œ~6¿?ÖÀrNŠéí föŸw•ŒÓ ?;êv¤/Nz ÇÝÜn{ &÷ Õ²ÈFCFïn %÷ÜVè@MN ˜­Êt°(&AûŸ*®+ñš‘ª‘V!~TQ£a&°¡‚Šƒ‰«˜f-- ¢9¥ˆ0ŸIHÎÒ‘ LÆm,ä3‘xÄÂÑaŽÈ<Ò®ñU ¡ Á^•Üìò“3¬‘`âÌEt´$ú‹‚oÒ îíè'3ˆhÖ¢N7óŸ?ìmv3Û®Wņu¢¦Eš:2¬¥×ö&òf_h—ÍÄeïV‰YO؈§;:Ô¿7–g]~ÜÊôjÔÎXé5XCK&‘U*刄0Ôª™ëUEHÂ¥Éñ „º`ÃGÚ°Æ…hCÃf]©œçÅ*…›ÕtMG+€p<ÊR¤¥'[%B9#êR1Ça"…\\Ìøk: À£ŠQ™CZ+¤&íì¤Å#r„K®C <6n‘»Ãl¯‚ÃM¨©Èî@ò©)3X»Bé2F;KÁÓµš¹8D†¸5íàq«H™¦2Ky‹8ÒCÄ7 Þ,Ê‘Q‹mD£ˆÉ¬nr.ãUx¿a&’Èz†pjb\æ£lÊ ñ”DùÌ‘FmtÚ#üH¨ÀF ¹T« N§tºÓÙè“vnsPØÝn¶ê¹ýy¯f½¶Z-ó{óöh W "úV-[+³š”œôµçŸßûë×Ïß~ÿØãWOB@þ¸à_žßûóSÿö»‡o_œ›éwAÿúÀ˜ó·ÿ <¦˜`cÏù;ù¿õf/àøàí ¨ê!äA@ùû÷OÞ^té@ üòü>ìzõÙÉŸð¼}þð×çç/?9|z28ž•÷‡ú¼§‚ š4Åí~þ`Z:Ù©oGãâ)ÃPÛlÓª ?÷¦åóÍÊË'»ßœo~ywüÏÓѷ盿||ôæ3sQõïŸì|yoøùIï³ãî'‡í¯ïx¶ òl¯ññAë|Vüp^ûô¨› ç³ÒéH½;)<Ü©<Ø6{îÕN&ún_>›×€yîîÔ6{ÀöÕ¡ ô OW1‡<}0úäátg šìæ@>Ü,ï»ÑtÅiWÜh;Ã|¯žéÔ°É÷ hVÊ|¬µ ¦UaFFÉÆ¿á ߟéÓnö`Sßk ƒî¶ÅvƒoÕØñ@î6øÉ0°SÝ©Ó 3é)òÙ›Ÿt•푾5Ö7'ÅìÚÍl¯#eÒÍ™a\tã€4ª¼ž'@ŠjBžvC¬–i9Õ% ©ºŽ ªTß Z::k2ûCi§'OóÇ3 P§–ÞìEPšB©–Wh'_Ê$VÒÑÛ æ3¨‚ï ³„Êlƒ}豆R—²Æü7É„ù¦8\@£æW™hd)ºÃ“ øªZÌBÇHAq±»OÏFHd#ì¼ÞЉ<b1‹Û„ @× Å|K%1>2¾~Ñc€[.fñÊ`6‘òØÀ¦©O»{ °9ŸØä𔕸‰C„³d¾ð Àfþbl”xÂN!Ëp!tµr<¾^—#EÞÛÈGz%TÄV•ôFEð”9÷@7$Kò¶Dw3ëê)Á^>ÔSBm9¨Ý|t¨£åÐ5&¼ÀÇ׍• /&}7id=Y)e£õÚ¯"¾;À'XhŽÞØ®ø¬—CÎëhp ôa× 2nÅ#ë÷í¸o9æYòY¯û­7üÖ›žkàÁ)ƒÇçRî æ.‚Êå ¸c7O¸ %.È ~Ño½uß¼¡â¶ ê vË`ž¨ûŽßjöÞ¤"–ãvÀz3êZB<+AÛí¨{ \2¸Ž…6ß2Єú-÷‚Ïr=è¸ã³Ü 9–”,Æ¥Ã|:(1À‘ôƒ9œ–¦ÆN_;œVç£b]£5>‘£cTÜ…G¬ âb7`ϰ.¥»D‡4!^È“U5UÈ"5hi‘ò·Ë™JÏ`æÄ†2é .¡ »³'§¨Ãv>ZO¡.]Æ: z óü˜Tƒ{)ÌF]<ïœã¸%ås6ßRþ‹ïËüÀ3îË`ž lêdÌ7qÞtÌþ.ר°Ñ,ÿl¨¦àOO†öÚ}:ß®=Ú©×ÅØVƒwÄ7Ïï>;nÝÛÒ7›üG§½±Ak´¯–àž'Ãü³ƒæƒ©þÅI·)„ZüiOúp«üáfñ»óÉ‡ÛÆÙ¸p6ÒNz2D‘í5TÜÁGV~jlp§šÙª0Quâéný›·ÆUšŽ¬4ÐëÑ~;é[Ì➢€hl„…?±Â“VQ0`l‰öUus©î†A6kt§ÎÖ+QJE*ŸCgãÒ §‚—äÙ÷Sed—xb:UÆd1^Ôˆ‚‚1Ê|"ŸMÂ-¶•ASº˜¦#ünþš–,+Ȱ‘¹˜nàz:º(ÖiƒV‰²àëѽ>o[Ûé³ó‰Ð6Ýj¤QKâl‘Ã-õî^ñhKÝŠ£³;‘A7µý©Ú1Òƒ:Ý,bí2aöów„f…’¸®S„C`nG‘5QçÄHAA3„+ƒ;œ ³ÊN[æ'½[CY†ŽÂÝÐ¥p]O‚ Ìg¼-ƒ´ wÊTÛ <ÆS^MJh¹Où”,þ2R‚ÁDŽu43ýrª]L6ÕØ¸‘œkhH'M lX*°ÞÍ6×+áí"n(ñf9µÙG¦%øu= í× ÌJ¢áƒÇÒÃ¥½å£ÁgÃRÆÛ­Ñ9ª ré7èN5]Vbƒ&Û¿³³)WEÅ.ÁN6åêÓ5 —Ø@IK {ùñ Ðkæf}}ÔRæÃÒÙ^ïþ~oZÙhËÛc£UÇýÒdX2Š™ñ°¸»ÓlÔÅr)ÓnÊ •"+gQELž^üóÁ~ûò߯?~óÝùëoïÿþêñ¿~zúëwçßqô×ÏÏþøá÷/Â&È›‹ôõ·&Û¼ùîÁ¯ ïØæ»=<ç¿}ÿðÿÞu±÷÷WPÉ£7ß=úíÕ“·6ß>øå(åƒòW@©—@™7o¸þxùäí·_}ÿ—¯ÎÞÉO_ýøå½þy璘îx:š r²Y=ÛmžnÇ“âÁPÛªf:PçÜVC€hå|³úÉaÏdž“ágûݯNF?<™¿ýüô—OúxÿÅ#s–f Hp~x:õtñÈÓyíãý&èo@D4ïç›Å»cõÁféÉ^ý|Ǹ;Õ‡êÉT0¯™ŠZÙ¦žjh)MˆÔT¬UNCÚ¯³{“ÌþT7e¢oör[=yV<œ7û2lî5¸ž¼7ÑÛeº×Uå‹r²SÉïTOçÕ»»Õ{r½_7˜rÙÛ*lÛ“ÂáNe˘O‹ÛãD³A~oӨ߬LûÚ´§Î§Æ|f´Ùb(êér‘ì4E‘JB¬Zfª%ºnd†ü¨¯Ê‚9D¿^Îd¬¬§5ƒÖ1l‹ó‘:4§¤€Xúe|Z§v:Ün_8Ûä5Ö#“ö­Û/'ËY5ÜŠ{C¡,RÁë,º’K[FUbÒH÷JIثЎ¦V1]p«¬« $ÚM%7Ò Kµ’˜ÂдáE]þ‡°˜gg(Uò±.ÒÚ*¦‡uVH»ïíY'7¬ó6›}C·Êfçm0mޱp„CÂe%Ñ.“Z6¦ =UQQPÂÞ n {‘iï;° @¤YÊ G±å2¾<hÐAh7Ä}U)X•BÛm¶®„Jxdàï™TðŠàM¯6sƒwëéõ*çhŠÞºànI¾¾ék±©•h§‚oô´D[AÄÄšW‘´¤w0ÿíTh‘FÖæ|•̰J÷ *“XWh/[ Û/gDx9Y†'gÒàŽ²„›KÚ‹BL¦|qïmµû6.“‘˜ûv`ãŠoýŠ{å2¤¾µ«q9î7î[T¹¨Â†ªy¬YL÷«…õ±e…õr) [ài–’@;"å@ <á6;y"kDl½¦â£fVã#^ËewÂ.ø/x ¸oF\7âþE4¸tÜ¢´ À–¸oͽv}åúåˆk‘FÝ Gƒ+DÌ0´_/аr¸À&³D\·HÄ x°] 9nHT ­"ÞØë\¹uߤQ["°À8üfέ õø6n¸V®$|àžœXÐÊãAøõ€ý³£Áõ‹Q.&ÿDÝ‹p‰ÀœqP¸ŸHxÌ+Ò‘øIºäþUuBj‚ Q2±wÀƒG,P,Ø€Êò\”NØ5þlÞ|zÜ?›õìé¸x<,ÔE±_w¤£±VË!Ï?ÜÚj ö§ÓâVSÐ_…o¿|vøâÑìÕÓã®ôdÛ¸Û—ŸÍ«Ÿ6?Ùo€ýÜ­óÇ=¨f¯)€ =ìHU6øöŸ§“"ÙSP€Ÿ£® !'ÐÎãíÊÇ'ÝQ…"ÃËM% Àóõ‡»•&`î<1“‚˘óë"Ò52³®ÊWÔdIB †H—bbÆWÑ±ŠŽ Œ·¨%bŠÀ,dÊÎÒ†p «*D[EÚø ÊLt°TýFFÃíº¹–´ÌùjzRá|fQ9¿@Ø™¤µ Æ>"s†p›z›«=Fs|”!<)ÄJš3A¹¸”‹DVÆu¦£›“ç@<»ÝãÚz¼_ÁzåÄüÿõ\ŽI¢îßÝ™©¥¶å-½÷$@Â;$zï]y_Õ^jﻥ–Zš™½û¬¹ûÞ;'Nžd"‘H@äi"kx·ˆ±EÛÕ)Ðùu=Õ* ë­êH³˜.iÑvkвÍÒn•ñ˜r`ŒB1ÿØ !-oØ6CMò”‡Í8³‚_“@ýú¢¾[56Ÿøá3>]ŠS)K ¾»Õl €®r¯¡¡¨,œïIøWÀ\ªhø¨®ÌºH¿.κz·&çe´V`%0Êb©À–K¼žÏÔjR­*WËb³¦j Q)xýdçþëׇ_ߺœçüññâó‹ý|¹ÿ§…(À*s&ù`E Üjæ`óÖk$kžøÇÇó?>œÿó¼=ùúöøZþ°€çâÛ‡‹¿ºñßÞžð|}{jÑÎÛ3ˆ€@~B £¿ƒ|¸úóÝÅﯭy>ŸÞ÷t÷­ÕÏSÿöN¿|ýhgÖV R²eR5 WùA‰VÙýžz26ùýNv¯­ JÔ¨Äì7äÇ;Õ7'½çsæyyÐ|yÔzsÖùüpüõéÎ/§®¯NZÏökÏ믭ÉÏÍcóçýêÓòIK†¯õª¯ÝåAN:òaC8hŠûMa¯ÁOªìA7{5+í[½LR§é©†Ž· $`•ÜêU™ÓYHfPã@ÆM±[¦!qÖQöúÚ´-O¡Àa~·-°¹5 ™šžéT„BéVápèx¬wJd^ˆTDcš”hU™£Âî8_³v¶Â»u«Wg0ÕêÖ…~Cê7åV…æéT…Z«'¯¥k¶^ã]-+%x&”Ë&599ê¨ã^¾QbT>š“&ðO’æ´ÆNy¢&€ÿe¼W@'µLKOÖµØ~WÎà©dÃðE講M®HøfY ‚d366µ’g]Á7®“Í|¬¥' .ã›mTQ‰ØÌRvßBB Hx9æ_ˆÓ‰-`0=¡E<±¦Ë`+…ŠjtØ`ÊZܚׇnÞÈ”Ú\°õ&Ñ­°¥TRì|o‘tª\€·æð8 'g RC91’š„xYC@½ÀQàɽòA"~¸ê?žfEò¬% µäy[z±_>ªs`?öshWE.9Ð¥ 1zP *WEìM)fîN6ùâ¨ùþrp5ÒÏÇù®‰Í7êòŸŒÌ½¶šg£xhS£¢ ÐZhƒNÚ:híÙ¹t!Ú¯s†ÕåHQK楈*† 5¡É±¢žnT˜nKjÕ…j™.š„,F[ AãÐÜ'¢ëÉèMºkåL·Ár˜ÂÅÓ1; f=t—!#53Ó,ÒHd]åÂBÆ—,ͽdG€L9Ñ,”H·€;àÃ{dXeò¼¯YL´Ê€Ë U=²;äËùPNp7Íx¯ŠUr]ô’¯WÁz¼n¦ þ@S±<‡Ì~X¾w§ÂzMŒYEä³ÑJ!Äçžy@•±¡B6 vA ¾¾ÄØ}Hh9[‰‡(Ü–S"BÆ¥ò~tĽ·ÌÞœxŒµu]I•tMØ&¢Ê© æM„בè&Ý$“Û2íϱI ¨>nJÁÝž0¨áM#1m35\ÌUÚÉ k*í³Wçƒf+éެã©õ º.Ó.8ý`¨ ë”.æ;zA— ¤7“²Q¨‹"< éå? ú“õʼ!¡2®DèŽ ÏQ ¤Kå,_æ"¢k é*Š!·Sè&0mÉÄ) ­–ØF#ë ÔÄÝž~06wÊGÓâN77mk“v>/#ÍŠ Ás¬í–¦(¨®ÓªJÊÖm›í¦^4x$îl©—&ÿýígþúèÏO÷Èyñû‡K+´XåA òÛû³ß?œýöþôëû“¯ïæòþ~Bâ·Oçß>žCäúЯo¯JøúN9ÿãÓÈ×wç¿Îþòæä×7§~yuüÅB +ñ›5ÔeÍ ²Öm½?ÿ¯/ÿñéÁ—ǯL&uéÅÕøÃÓƒ¯¯Îß=9x°ßº¿ß×åÃ^~Ö”x£žC‹`±ªH¿HEùì֥ݚx54_µ?]M~ÿiÿÃåðíY÷ÍiçÓýÑŸÏÿëõéŸÏ¾>ÙÔy4)7¥ý ûâ þË£é«ãÖ—‡“§³â³Ý2„`¹œw0[ŽšâN…Ù­öˆÇ]õ¸¯]N‹§#TGÛBHõáh7{6+ ó@ãƒÜ¬-OÌÝžºÓQ ñh¤_ÇÒŽÚAW=›–Æ @#yÚÍ·Kì´£Íq]8›š}uØwzZ¯!»ÐÎñNag µ„Hy j²×Æ]åx·<é©§W—ZU¡Yá«J“’Z6Y)Q“¡>äwÆf°ªÌŒ{j·.NûZ)gÙP%Zr»Ìôj|­@ÕKŒ©¦rb^]‰t””H3Ÿ¨e#ƒ"Ú5“Í||§Å´Í¤‚o·TYÈø“XÌ’[Á[–ý%ÐÈE*?²\ÃëZdR'ú%D!6tuÖÊÈ™-%câÐúü€ÅדÁåç^È»àwÝ“€=K^Ûw ¾Õ.ã£;¨ÓÅšuœ .„œ7Ó‘µºN€þhHq"¾Éaކ‰i¼ŸA·òBÐZ“Ž[À#f<4jƒO hGeƒ–wh%¡KQ…ñ3i;_‡p„Hp d+çHI…–Èä&¤C(ZÃÜ8]Êx„XÌß~6c7Xo3¯«QÍêäqÈØ¦É¸ª’_I¯Á›:e/ñn[o©¡,¶®[ ²Zd\Ê¡a€@.1±¼SÆ[r¨.ð¸MÒÑÕEÊ-$V=+Á#‹À<¾Í¿zÖÿ!h×L|=á½+áîýG"²Î&í)ß"8[7ÿRXÑ·í¸kí?=€Voý°ø!î]&ãvhÛEno/þ2Ðȶ)ÇñغƅÀ’àñÛ¿Kïßû¶ÿsî’Ú• Ûü¶mÀ(²­d1‹@£3÷N|‹M[í&ÄãÞ™òöÀ!kêNp Úñd.,jG‚ÛaçŠok!ät¹¹µð{ÁÛ½td›LX.e¸¬-Ÿ¼›?8W¿s­}o_þ+p\õnÞØ¼÷—» 9o¯ßý‹ßöcÒ¿„Ç6PËÃáMÿö ¨ÛBCëAûmÿö¹ÜºW€¯‚Û‹Žå›¶Å®58tϾ|#h_@Ã[QÏr2°J&¶C®{î;í›AûÝTÔ‘A| ¬ëaP0¥Ñ¢‚BlT—özùn~ÒÒ*9BÊ„yÌŸ l²¨?`¿Ë ÞºÁÔtºY`û5ÅR¦Œ/q˜·¬a†œ‚pÚÑd*(’~L ß'Q!€·:s<  )*í¥ñ@Ø¿&³±xpãºo'XKÇí@;â)D°„h0 òÃÏDp#âY£6°|±¨-“pR@DqDRu„ Žr€U '„TÒ)^°Î4&Ð1IË6l);5ᤗ^®žE^\ôAyžM ÿýíY3"Þ»µ¿¾4þôtÖÓ±“žúâ¸ñåñäÅQý¸%Œul¯Lwä謀?å*Ìã©å­­$‡:þd·¬ãîi1ót·‰|duÇ.ú¹ž†ŽMòœ÷´×W½‹™‘ößc[?t .ªÁ^‘Ç‚›lÊ#áþO5]Íc`÷1¨=/„%Êc­¬Ì#`­óTô´®Ä;u¶Ya€:Öm‰»³BÑÀ;-i·T.’Ô”X½’9?©îjÅŒÌDøL° â2…ç.Á¢ŠUò8<809MÅ’ƒÇGÄ·øa^xù3I¼öí"¨¦x½è5Ò­RbÚ¥öGüáD,iA™±•Õ`¿Šµ  …¶wÊè¸EÕH§Êjb¼¤a,éá3^–p+\¨V$ôllÒWfCµÛ`ZRφñÔ*O¹’‘UÈü·ß2Èz/) ÐH ¨¼/¨RhN€aCŽH¤³(G†5€']#Sö¼œªÙdl›g"²À€j¯*ÄYÜ GÒSRS`N‚Ôt€1@>Æ0TKh9Í ~•…«yÜž -è&G{Èô6ÔMa=¾m*áN Ûé µ|B$ì,f›;5ë$;…{™Œ?ÛˆGWiøÇN¦7%Ö#PN°j™4Y›StXfmÉàü igÑM™õ˜¹K{¢á%4±I§]y1>j*G“â ZÃ:o5|meT÷ºÐª2ß©ŠV¶Q•Ê%¾ÝÎW*2˦²2Y))•¢¬g3æüð¤õéÅá?~yôß>ÿöñêÃóƒ_Þœþöþâ·kàùxõ÷Ï@þøtÿ÷€4çsà9ýðœþð|üÿšµçÍ)@ðüüjuæ\þò è í€\ç±®ûîôW°&^}}sòç‡Kž??ÜÿöîòýÓý¯¯Ïß=Þ{kÉþ»Ç¿½¹úüóÑËËÑÃúñÈ8èk³–Ü+Ñ ò@³^å§5aZáÆEfZfÏ{ùç'³Ê‹ÃÀ 0ÏÇ«! ÐÎß_}ûùèç½êe?÷l¿öþ¢¾>ž~¹?|Öæùy·üò°þâ°þxZZ×Â&ïCÃJº[Hí¶™½£³®<ãØë2†èÑ'…¬aщö²˜+£c#\ÁSÛfO…—àshYß]Ha<`VdR›hx)ê¹Ë ö†™áqHz‹JJ"½€"º¥kÇ@# ™öeRÛ@; ¦·²Ù Ä!]¢¼0I9«w/P äϤ¶€| LGWYÌ Ù€pà,`'°•®ãð!· 骑ñí’lä¢bzÃ`­Õèiï%Þ[`]xà¦É8sä6âúÏIk(¡£Wâ=\|QMohgCB¤&øÆd\@§El¯’9ë ƒ|RA62‘¥ª–ÈR®L| Üóoý'Xà^3„hÜu7d»‰‡7žÅ”wÉ·yckñ¯©ÐŠ{ãoAçMÀ?ßö!žÍqï¢ÊFX8ÄÓ“§ÄY&xšŒjþ?Î܉´Ãgû›wû?>A§ébÈš˜]¶&@‚~ .Ä} WÄ7AɧËtÊ®±a2¾ 踅EÖ“%ÛÒ_!žÝ†‰¸–œ«@/7}Û ÞÍ»Ž•[öe @š{hØ™ôoGÝ«HØc5h¿‡E¶ƒö;aç] Ÿ„oųñ}·õ,@±?PfŽ_aр퇠ý0oS`}ûá}Ø~Œy–´ (÷\ôÇÊ«7ÿj_úÁµzÄ·µ8g­ïár¾í;÷þtÞñÛn†]w!LW㾕Xp+¶£Q“0°KdP¡CêªjXÓ š&]Ó3fàñ@&éQè8›ö±i¿BÃû†å…ÂDàtþó´‹Áâ/ªiˆã1xµ ¿›B\26•4Z½:)k–ŽÆü —Ȥ½dÚÀÑ!4fƒCVWOÌðƒ%“DÜVx2 ÕÞÂb6@/kLÍδA=åL"DÌNÄ’u€L¸_%ãë˜Ä°ÂLBEŽ÷ ™£ŽzØÉ¶5ìþ´|ÐQ?ÿ| ãnSˆ‚Ö1WÌñ£ÚÞmðÿýåASI\Žõ7ç¾=ýõéìÉNñÙhÅÚ^9³["ßVŸí–Î:Êå _¤ý5>òpRx4->;l@øü¨Y¢M)þó~í¨%?›JÊÖÕЗ§' )mçS¶g烶‘)ˆ©WS6é®i™¦AÓI‡B…êÙ,dDÒC$ÖeÚ[É¥Jj2ËøÁ„©[}ÝéN©ˆ¼7r©Vƒ µJ)S-“5˧Gfoª?¸hN‡òd õšV[©KÉrŽÌ )ós˜ß£œ{¬æ‰ªNNÚêÅAót·^7è,…Ç*’A•‹dY0š¢9!QÕ©v…™t™Ù€wÉÝÛ«£“.0C"/¹5Îiʾšm—R†àmš‰I3S7‘N…%Q—.#bIŸ*F%ªÛàÚ5zÒS$ÚÓ­Sç‡%Mðc‰e]ðp³QÎ¥K*bJ >í’HhìIWnV2<Ø_b¨”KÖ ¤i¤»ÅLUKɯÕ{#&D&¬J)&J§Ü$îË~]Å 5%lðŠ²¸›'ö ãU©›ér.Ñ,¦9bKã}ã6·;P V— ãã 7_S¤pÑÄèŒ[`}µ"¦° À·rœ/Ïûëùd§ˆY›G !kÇ=¦4Mú)‰YKì-w‘hr M¬$£‹¦5”°L»æû¿‡š&1®ó-±Ö¯É’‘ÌÊ!Št¢ÉMsÒ`“ª©^MØéç'ÅÃÈé^å` öõFžØíå %5íý¶Ömi²Œš&[­e•,)Ix«iµû³'—Ã'gÝGÍ×Ofÿõë“???àùôòè÷­N ”Wu\ÏÇ‹ß?\üvÝÉóΈÀÏßÿ%ÿŸôù¡K l¬‹  bÏÇ+ˆ\—Í9×yàº_üúæèÛ‡‹?àBs"úý-ÄïÿýãÃ~züíÍÕ—g'ï¼y°ûþñ!`χ§{¯Œ_^ ?<Ýýúêä—G¯î6{¹qŸ6ÄÓ‘q:4&U¾•ÚÙÔ~Mx46_4^4 |:-=™–^6¿<˜üñÓþߟ}{º÷éj)?íT~Þ­|8ë~<ë¾=jBäׇ“/FK`¤<;¨?Ý«^M §ýÜ^C>heÁ>º˜/¦¥ËÝÊÑ@?›ÄÎwJg“°ÍNK[SŒ„ÝNöhßïi»`[5áDe¯)ï4ä½pŽy8(Íý^~·­Â-Üß- r£š‰G“R·j1ɸ#ì5‡-aÔ–Z€veØVº hgÜÑmuÒÓwÇ¥ÉИZ…¶V¦Ï×síìŽIOH[¾¾N™6åD5Ÿî”èI[©™$,4hðÃ;k §“Üa_žÖ3»-út(*XU ×ÕhQôë¬}€ksȪɒÛjÆ–gœƒrzTÅ'u˜§WB+Jp\#N&ò¸Iö«xUOH7G¸ÄŒ_ÈRÑM,iK„VDÚ_Ê¥ªFªª'‘0Xå‹YÆWTc¼`às˜+ǃ¡ç4¤D»ÄTs˜!Å{ÀôÈ$7 ¾ _Ì&šÐT-ËgbMZÂó¡.'èëù<×ë@T.Àƒ†W¬Å‰M ±åÅ(YÌX@G­^ ÂÚSCÂmE1ØÒS©(!2rO­°‰% ]Sñ-ØjiÑ‹±<,¦LÆqÐf ÆÝ3R%Á¯¤75ÂVƒ;Ur¯N·³‘¡‘›è^…hña>a•÷!¾›¦Ò¯DØéäZÒwGÄœ•l’ˆniWÊ·”p/R`ï‡7QÿZ:´¾µô7žtG½wÞÛPs¿ýGà"¹m¡t˜Ä¾ú7ó‚rK…Ö‹*ª²¢ :¾Ç¢K íª 9ohœ?ê¹tÞHGVDÒm­*À¿±!Y·ï£XµHhþ|]ŒG=÷ ¢2¡ Àæ‡yŸ ­CÓŸÛ\k7í+?¸Ön;WoÙÜ^üÑž•›!ÜåÀ¢ö¸o-ÞF‚îƒö»@>©Àškí{çêwIÿjžcÑMûÊ߀Là¹Ã¥Ã®;óñ¬Qïm+´æ-XÛZÛ…Û¼7ý[w‚¶…€mÁ»q7êZón,8–o:Wo¯ßùÐWÔ½æÝºe[ù›ß~+ì¾ËÌïBx—¬UZxZÒ(™tq Û†:èBÒSF…8Ü_ÎeJ*‘ n%—¨ä¨VQÈ$Ö F¢Bâ†Ã}l•øq[#“v,¾UPЪAêRTáÍϤ£ÛÐä¥ãvzÞÉ“ŒlE«©È6‰z@ð¤SR@Aéy—üD­9Ì[Ä“.HÌòI‘Žˆ[aã¶Iø¬¾5•‰‹dˆJ¹¹´ð†Jº )ƒD4¼?©¤¯æhÿÙØ¼Ú©á56¾5,1ûmµgÒ³š4­KN'l†q¼ßÏ£¾Å¶ŽƒüóýÅùHŸU™ÏÆ Ÿ ÏÚÒÛ³Öçûý¾šlò¡W 0K`NŠ””Øêç±ýÿòØÚîâo/ú.ÔV’ 9ÛÙԗdz‘AéÀÕÔxvÖî—¨ºŠþtÜ=š*Üï™Ìs C@È„Ãò÷ˆºàK—ÄQÑ–Ià…”sh³”Ñ„0O½D6+´ÌiÒäÓm õ •S¢e÷åB>QÔ“Uu•Ý¡q<«j|ž8 (OÐ:Ò±­,Q('Ä­M2’’æÒ,2@>åœï¾8úóó}(ê—×Ç_^Y´óÞ¢|~ôûÛËßÞ\üñþþ???ùóÃÃÏ?Ÿ¼}´÷îñÎû§³÷OvÞ=ž¾}4}ûpüæáä̓ы‹þëû½<ýé¸}5+Ì£®:2É »WåλÚ|>O÷ùAýÙ~ý9ðÏQóÕqûóýñ¯f¿<œ‚¼=í¾9i¸è¿9nYÝ;WG­WÃwýdzÊÓê£Yù§ýúÃYåá¬z>4ÏúÆQ/÷p¯zÜ׺êQ?¿ßÍ^O«ÞkggM°ç ¯L Ç#}H©«÷sG]mZ“ö»¹ÃyÐ7ŽGŶv22gM¬08xé|§ò𸽠Ŏá;5÷†¹^;˜šÇ³R«D÷›PJ¿):Ú¸—뷲Î6§C³\ àý4sé" M]˜ó{c3'ÇÁd¨Dßò#¤ B§Â´KÔÑ´8hJ“ž6œO·5˜^™ì—‰º5yo·ˆìµ¹^mëÉ~)]–eŸ62½2 Gó¬»$;¤ª† ÞSV‚ç3uÖ¤ÆuR&¶„ôFžqí÷„q“ªå£yÁ›åÆ`0Gøˆ”CWø % W/¦‹Z C@‰vÊTÝÀÔõÜI/™ØŠû*9¬¤¢ÐƒÑÀÃáÖd›ëÙÈÖ–µÓ Hcmhà5«äÁÖ¶åÅHNˆˆ è+‡FÁ e^+O…ÆÎVÉ!º¦Ÿ'œ4—»ƒEÖÊeJÛ,ˆ]7xoY àÔµ] £÷và–+Ÿ\ÒiG;— :ºT•ó:U•‚yÒÑÍ'[Üa‹­‹ºœ•ˆ ëÓÐ-KÒ6)½­dœhð^:´@ÆVðÈ’ˆ[^g…´#\Ž»ïF·£Î»­[ÞõqÏàM£HÂÍ:Ö¿C#«éø:ü9H&¶Áe×ú÷€Ë±ö½cåûˆgѹö]«H†œ?ÚV¾s®G£Ö.ÆŒznáÀÜŽgó;eʧ²!*ijB‚VqߢßöcÄuW"\ÚZ‚->ÏÆ 9,kø|héÜLø7àÜÚ—mË?¸×ïÆ½[ ¶% «Ÿ'ä\ölÞv­ß$.h—=?À‰Xd˵~#ì¼ tq]x»ÈªLâÀWPd`9Y·úüKð“ǽ<æ¸BÎ{ÉÀºgýfĹ4÷«³q®¡A»gýîöÂQ×FÀ¶q­ù¶ï÷ö»Þ­›n©ÍD`‹m%’¨BH8È”;ƒx€C®§ T˜% FI¨ˆÊ§ð¸ƒF9¼{zqÜ©¨¨ÁEŽú¹/?í~x8úpðÛO³ßšžw¤O—ÝÏ—½ç»Å绥úi·ôì ÖϧRìj¤?šG&ñd·|Ñ×Þ]ôêbÔ =ï¯/OZOwËûuþýÕèËÓ=Ð5%5(1]š9:r2.K¸ŸˆÚslBÎD¨¤npšNºd¯Ra>í¡S÷åà ¼¤¥A 6ËŒ&Å 5U)et-Å3¾²rˆ©% 5!1>†p@ÄRqm¥^d&½ü´ohbRf£ðŒ€Cš¾Ã ¬urª¢“á!â[e -g‘¢œ*)É¢×è@]C B¸¢E:e´SNWrÑ^·è†‘ê”°j.^Ñb`¹´L´]Äj¹„µÜÕšôKj<Ë…À«ZηÓuƒl•˜ACª˜DÅijB¨]¡-_µ-no¨ìŽåA‡)éq™usñv•µùf…ê58™ hb¬lཆTÊ¥)ÄÉ>:í&¶ŠN˜jZ¦†Ó–;¾$Ï„)П¸›ÄÜÀ- ™´S“âå<*¬Âº‰\ÝDKZ¼f »#Ë\³JíÍÌn[di_^Ct5eª(Oúá**©‡yL)¥±ÑŠ–Ö˜0\ééÓYI³æcû(Ôeæ ]EÑ” In \˜gƒhrK¶ÜžÀ›Ô{YM””¨Bº4J "¦ñ‚š*æÒF6ÅSHŽôsd ¯ÃÕXFæ!@NK9èg“ÒѰ¸×3:e©WÍÖLQ“ˆ’)h*¥(äxTë*’Œ›[ÐÈËÃî—7WÏìüúþêÛ燿¾»ø òþü÷—|¾ùãð‰52õåÕ±Õón>üôöå—7'¿\OÅy} )sª9»ÎóõÝÅuÎçWÖÑëÎ"HÎùðüðËë“ßÞ_Î! Ê¿¼î‚d†CóÂÏæ^§ø ×»Ë¯o.@~{wyÿõõÙû§;oŽ^] _?½¾¼}0þødöééî‡'³7—Ãg'­§w÷Gïî!rÜÉŽJT'‡ äAK¾Ov+/Žá¬Æ€òt§òó~íëÓÝÿóæüï/O¾>ÝÿíçƒwÃ×§½—ÇGÓòÏûWã¯O~y¼÷ájòâ¸ót·þxV}2«Aär`œõ´«‘ù`V<觃Üå¬ôh¿z2ÒwZâÄÚ~K˜¶¥ÃQît·t21y  YÞ}À•º4iÈ`XMÒáÀ‚¢YG9ä÷¹iÇê–æ©¨~Cžõò“R·.Úê°­Î@Τ¯ïŒŠ“¾1£^¾U—f³Ûú-ò@΃iq6ÈëJ ؾ]ãFmew ï”f}uÚSÆ&@¼cÀ?ã6_דõ\|ÒÈô…v9@µ•Q-3¬Í\¬"v[4€Í F*|æ9Æ]J (”(èlG-)*ií® þ“Qn¿¯˜rˆH¬èRP€f=ºœ -«\˜Hlãñ- u0ó¹7áhwAj¼Ô42lr¸ ¾J«›EŒVu,/Æ@cÀ¨\H—b@5FPU·v8åqg%X»Ì !¡€ão—Iø¨Éĺ”ñ2å*f «åÀ0^æA$ÖdËÃDlE$yÞOÄ–Èø²B9@(še€ŽkzœK¯©´Ã} =>¨`íBª ùv;,@ Œm–eÇLuMDHmJÈ]á“kBj­g --&£M5–#ìLt9›¶·µ”†»cÛ7¨ÐjQŠq¨ ÜÃ#í¤üw¨ÄšL:u>`J*±w߃& oxÖn¤Cë -]yÜÎ×LYÌYHÛL…Öxh7#¶Õ[߯ÜúÞµzkíö÷a×^£Rö, ØoÀ€2˜ÙZüâHëw¿‡ e- œ“ãc@ Hp-î]Ú¼÷½HøÓÖ~«ŽÕï­YÇö;Q÷r:¼åZ»‘ô¯Ù–¿‡¶uõö÷!ç"´¼XÔT3Ÿ®c'â.çÚ¾­;©À¦wëÎ<Ñ-2€ Õƒb#®{ªÁ"›a×€÷@%Á®‡ÚB•"î»pi$´íXý1î[woÜŠy×â¾µ˜w5èXˆyV ó¬y6¡üEÏÆÝõ;ßí«4Júmaçªoë^Ľu »CŽ…tt ‹mÁ¿ó-à)WÔ¿–Šló™0fu§8LP…K3¨Wá“õ¯‰üì7T•KÂ…°¨½ar2Í )1f1¿HE%: ù¯2³™P,´!Òaøôše® a­ mƒ{ï‘覅=BÜÚ¥šð±¸7´ö­ž¹º‚ÿOÀ<¦ŠKLhÒˆ@X˵Îÿtã ám ŸnEÚé™2ÂàÁcŽëˆÊÆuàѨ`]ÅÚzfZ—óL¸[`¨˜O¹€pyJç“™¸ƒE¼â-«Ä¤ËÄmXx½¥“¿¾8n籚ûpøéÁð—G`ž™<<™èßžL^ÕOÛÒ9zM©Dûgeú¼¯õTÚ«|ýi÷¤£ kbÏ^ƒÄi‰:é¨ÏO;‡m€çÉQ«,¥@òˆ«_@d2”ã“›ˆ°Æ¥¤LT BdÌ¡aƒO©tL ¦ €w ¨aÃV¶jfXÂËÓ~UŒ©`Öx§Îµª €`OIO[žçKT ˆœ¬¨i_]š“’FmWøz‰iÖ„rž;ë°|2JIlH¢y!VÍ¡u`& ÑþŠïH“6òI œ~•ìW¬uL9 ‘|é&fŠ¡ª–Öèi[hð’ßf­¡(ÂVFANhL¨œEu1®K‰ŠIvB­HNºr£@”ÕdÃDËz¬^NµëxQót›Ô Ãφj½D±„G¤ƒ2V…8`‹ûÊz&/#Ñeð¨X“oᥠ/Cù8& p!†ò“˜S KdrÈ¿¤¥ºº]"»UŠ#ìeÝEžñÖ*T»)p\Êx¡4‰ ÏÝ#x92(Ò…[#ƒ¨¯a2²  ñšN€ùlH±rEã›xÊŽ!4iC“vÜšÆìãèÈ„àÒ *D‰‘Š#À­þ±om7).8šb`)HtÈPÓ…¯‰L9Lyþpv¾[?š§ãÂ)Oߨëê{³[–G }Ú-÷†"àFŽm6ôVK¯7ÔrEj4ÔZI˜ö G³Æùaç˳ß>>øöéÁŸ¿>úüúøË[«¯æÛÇ‹oŸ®¾Y+¶.{wqM&sn9ŸFÍùäu®Ó/~}{~-0só%^|›'~µJû¿E] ü¼.ð:ó¿ ¿øöñþ¿ Éê‚B¾Í~}yôåùþ—gûžÎÞܾºì¿¹|~ºóþáøÍåàíÕðýƒñ‡‡“÷Ç€=¯.zOëgC}Vçû&Ñѱq‰ÞoJ&…7çý/g﯆¯N;×òì¨ùéáìÛ‹Ó_~:øðpöùÑî‡ûÓ·£Wgý';µg­·gÃÏf_î¾;=Ûk<Ï*&EÀ§g‡Ç»•³An §Âîw”ÝŽ|4ÊLý¶ÓW¦iPcŽúµös=@‚ÜnW¶•c`¡‰y46vA3t²{Ö*u}§Ÿn‡/tg`ôëÒ34÷'¥Ý¡ ¦Ê¸›2AÒ³B@ bŽÈI)°1­,œØo*ݺPÈ¥ór"µBÌŠaGž–ÔAK.ê˜Ì…k6ëˆã&=mR‡CñlG›u¸NÖ©¡µ…DÌäuÝJúî¢ÁEÝV2.‰pð˜Åi9Îlà «9Þͦ×ÈÔ’Æ»M%Э¤Kj(ÇYÞÆXd5¾ÓÐc¦ Gl\'ÛfRç<²%¦¶4ÂU“#áÐi—FØ€|l› /ê”× }bËWü«7ük7bÎÛI、o!Z&ck@;tjÈ ÜÅÂKlj¬ù6n¤ƒëTÜ¡d†Wè @K*¸‚„V®»óž>•Õ8Pg3h[ñ¬ßÛ¼{#æ\¹÷Z;kûm·’þ•Òˆ+ê^ØZ¸õ,ªl8ÇÇËvÝKúWƒöÛ2 #à¾N‰:în/Yxƒ„61<ëÖä×ÚAÛ=ßÖݤo=á_÷Ûîz6oZ\Ó´ÅQÏrÐaq¤»ÖD »–ì+7œ«7\ëÖš,$ô²Ì Nà+ÀÏÆ˜w ­ÃMá–«ËŸÝÎ$|¾í¥¤Û¹z3h_ 9—ö{î›Þ-€«»ðÓ·}/ê^ :–mK?†«ž{εÛîõ;4â—©d€j@~kŠKÂAÄm©à*–tŸÐ˜åw7“öñTˆ~BDdbÊòIà‘†—“®l&éàaÓþdhhçz±ƒ$&'Z‹­Ò>– É|B`¢$âÈöZUøÌ›Žo‹T˜HÚÉ ®ë…·èšïwöl0 %Pa4fƒ80ÌÆ¡|>‚*A K¬Åéqç|Ð* õÁbÀ±ñ^U–©(‡é˜Ow'ðxJy"îÅ¢”¬*hCÃÇUÑàbý'~SH*p›À{á-Ôr.ä%ãN CÌs ÁUÀ¤‡²Ï“žóaîåqã¿ÞMLìéÄøóçÝg{¥ßŸNÿ|¶÷dV¸ä›r\Eí‡MñbÂi)Ég‡õÝ*7.` þòdççƒú/OfwJ»5¾o;u±ÀÇ4¤´çbZiä™ ÞßoWT”Ò9. 5ç° LÅá¾D,hðHY!L<ÓÅTŽOÈT°§fR­ W/Òõ2SÔq…ˆl§|y%¢ðaÀžQ~ØQrrKló”_ábÙù@¤€£µ"“Fœæ¥É mm¯‡vø´n§Ìõë⤩å¤)Æ:&Ù/Ó`ž4 ´¬Æ[f" í rZ0‚ˆ–‰—ÕȰÎ6MLeýûãüÁ̰&Uèaod¿*¨\„Å=ÕB¦QaT)ZÐ’å«éÓ³ßdwFÊl¤4Êx«šé·¹ù°Å.±Ã=Bµ¡ò/ ¼TNÀ…ðv©|"ð‚r8KúÔI¤] $æÔáÙ0Ç„iÒG¥]"eGQE ~ÔÇ-ñhj¨|0'…EƇ$ÖHÂ) aŠò ÖÂÿ8^rX\&¤ @V!2é’¨°LGL©dÍ ê&‘—bÐÐðt€ËøeÞòöÆRAMA:MEæ"PùªA4‹…ñk,ˆ/ÇúÚÃN• ÊL`>ÛÁ· P(+ËE9 êxV>'“âý½úƒ½úŬz±S¿ÚkÍ:Æ´S8Þéœ 9®Z”‡ýJ§c6Z¹BIPU|oZï54`ëŸîþñåé_ÿùË“ÿóíg xÞœþúîÔ±úhõÃükTëþ|œëê«5!çü_ý0§×‰@#óÞ¡ÿ‹=x}Ê¿çú¬k€ù7Ò\—óï£ó!0«§__ñ«µ|ìj<¬ðãÕïÀToN·8Ÿýöæäó³½OfŸìüúâðÝÃÉë«Á{@ÇÓ·÷Go€|N>>šBøâ¬{5)ì5%kÊqS:êd'ef§ÎwÕ‡»•—g]À¤Wç½gÇ­§½·W“×£÷÷§¿¿8ýÇÛ+àŸOw€|^öÚk>Ým=ê*@;€=–—æ «ñÑr™u¥£qöh$ït˜q3Ó¯*ë*f#`¹¨”3ϸºF²g"9Ö Ÿv–òäù ºˆ;RÛbº[NWs@‚ÝoJ!•öŒV¬›ð¥“†a1[Äs[ÌxÊ9,]Ï v4²–"å<’ /bñÕV™(å€4†œÈ‰Q ´òÑ¥8<é)d“À<™£N¤¨!Ý* ¥0>1ã.Ï·ÇIW^ Õ„)[®G!n*1€¢²–,(q)ãáÓ"º†…W9Ôžç‚*í+gã5QÏ#f“H»Ê¸²´BõÔ¤!h…$ʦËþ¼à•269cË$– Œ{äÐ52¶€‡ï€ÑËUQ|M¬—Äð¤Î¬ / ¾[Dø„$ã“1G–p¸ˆ„z |<°ù#\I‡V޻鵓&9ÒáE:¹‰GVXÄΡ®„g1é]Ž{–Rþ5‰ô DÜwƒŽ[ ¾­›–?ÿªBÅ$2œôo"Áí„oËš<ãH¸´/Ä›šû=u¿¯õƒaž×ɸp<6/÷ª }ÚÎκ*pÐÎɬ´?2vÁVjIµyqÜ<Ü)ÚJ¿%ží×fƒÜ )ï!ƒ>íª»ƒüÁØÜè³N¶_áËZºT]¶-o„ o%ø|D0‚йtÕ$Ë>½´W…Ð(S“˜òUƒl˜Ä¤-œïê—{ƬÍtÊh5Ï2.@—Š/Ê¡q-sÜ—:fŠMmÔòHQ‰™bhA"Ý霶¹‚ì–ÑV!Õ* †BCK¦7¥œ4³Q³2°ºK"™/ðBóŠÇ73©m,¾Æ¤·«R/¤§Ý츭áp„˜BÀžªÃ‰‚@†ªŽƒ±5·ØF5LBøxá *k)ˆ²qø¼U$kÉU æ¹´ÝônÜLùŠr¼¢¥46PÍ¥Šr”X*í‘3NÙæ)e#Ål˜A׋Z„#6UÎÍaÀ?€v"±EÆ—²´£œ ¥CwÿMSðD&¶¤’.µéŒ¿$F¨ØrÒs“ÝãRk&çÓY/eâZÆ_U“éuÜA+1÷]ÿÖ͸ç.¨J•h X@ÀÜéÐÝäP Žgíf»"ÞT â¦oÛ’sP„Aü  ãßæBܳ°-mܹq®l‹"òLŒb ÛÒMïæÍˆ{PG¦ƒ­"¨ðI#öÁcÀ?–#¾ÈàÄÖâMfé´AqïšgãvÀ¶ÀBÄÜ1Ï=ÏÆkÚÜ€}Á¶|Ó»}ÀÀò=è\„çÚMø ÌsM; hÄæ°sݺö÷­¡áMÀ¡ˆ{‘Œ[ «!ÝÚ.!²ó,[¡—o2h Ùñ˜Û½q Ê*ܵèݾuMYî› ónœeïÖ½ˆk È'qø·<›wÝ·¡P¾góÀÞŽ¸—"ž% ó<ZPiG†IÄ‹ÆaïJ:îÈ‘Ø8$Ìh"’ÑfQÀcN&í/k™ŽµË’.¥%**11qY<'¥UÉŠšpT¸Uv ’å ·¡‘âI0Ÿh¡À$»•Áýs¿‚–sà…Kˆ´å ˜˜ï}x#Ì; d‰ 88#ç<–m(tŒF¼<,æ#¬•陎òx€ÇƒÊYÉQ†”i~ºßÿçË£gû•Ó–ôdzýgõ¦œèjèÐ öjüQ[~0-œõ´·ýq!ÓË¥Z2ºæOÀÌ,QÏO:öjmšR3~1íÙïæyR¥#dÌ/[EËÌïÑÇ!¸5x YÄ+â¡‚ŒUr¤)§¡†<“n® bY6’,¯b“ìÔ„f™)åÒ–GÖNvÐ{uÂaKî×Åf‰ÙÓ^¾_—û Ù̦yÒ¯I)-›–e„a",ØM@$6*s1€U`*„ÿÙ—ãcU \VÍ¡ý*Û-Ó* ·)Å`9šD»hm ãÃ<æÊƒ¾Êa½*«òÁb>5((©n…‡ï®Uâ äz‰)›˜¢í £ñá¼±æùøÜ[<ËGUhëM²µ*\!‡ib"/§Œ,hr†D\©È€Y&í$»@ù[ÀƒÆ=,éËg-à™»Ö+bJ•€:À9‰OÎ Í™³Ö°T-P¡ ï›§Ê0Ÿ–„Ex¨Œ7¯¥ó9,§¢Y9% 1(Aœ“!–r‹lŒÂ¼ E—¸0žÚÔ$Q;žÜ™ CzÙŒ/—E&œŠogà¢BLdBpkpƒõ6©€;JÙÀ† %•—“†ê©bÒÔp°„L fPÍ=¬‰g³òù¬r24¯vªgÓòá°p2®ì˽j¶Y„g›ôJý¶Y)Š­†ÖlhÅ"?þØz &Ir,]ûGÏv÷%S0383Dxx03$3gUCW5ÍÌݹ¯Gt÷ÎÞï3;&“Ëå’œtž#'ý⸛¯˜ì¤—ï7´£íÊÏ.ÿû×g¿¹þíËÕoŸ/~ýdÍ¥šÏd†%ÓÞ¥ÿ%ߦxóg/fòåÍ Ü¿Zxf\4ë¥Æx ÏÌÀÉjð9ùªù¨øý‡|ŠÅEïN¾¾9ú:[ÉçÝÉàÓ«ƒïoŽywú+â¼oð¼D»éÄOØÙôº@m2(y¸œM´[ »ïK´‹OÛ¹” ÿˆH9s2"D!Í_ÅVwA‹äå ì)”ª¤ETÚ©1®™k>S ÔŒJ(¤ÖBG—øÄZ-›ì–˜©-ÉK\hJ~‰Ýùê¹x^òóÉ•,ï*(`€{2½Þ*ƒ²¼µ|¬_£&mnP£²¼S¡ÖerM¥ÖÙè|Ôù7"pON¯”d¿œ\“«*î(¶Ä„©ÄzQòeÇ LäX—[ÖI§’²k„ À“öΧ| q÷£àÆOþõ¢® ïÃt`A&œLtOlà”oõ‡ÀÆ=×âAÂŽGΚ³– ,Ƽóûƒ€ýaÔ½u¯Fœ+dص1÷£om.êZYúûß‚öCH€7ÂÓ¾¡ ãaØõpcþožµÿÒ¹@NŠT²)÷O‡/ZƒtlÅ»þ76aOúW訪§–cù¤;æ]Џ"®E0 @¿ñhùþß‚ŽÇÂOû¼kõï¾~ÛCÇÒëóƒ'î[ 9ì‹?l,ü-`ó­ $â^–WÖþ͵ü* ÚªÅÈB"ýðûm’~°ÍZÔ½ˆò­ßÇÝ…‹¾u”|‰# «áhõ÷ÚÏ|Àðûѳöw"Çsh¶A¦ÉÀzĽ„LQ”%†ÙÿËâ+ç˜G壉0”…] –ÅJD:XÈP IF6À<9aªé‚NJ$ž¹«SVZe©šcÒÆ`ÈI… Ë\´ƒÈ4xƒ„üK ¥ZÓ„G¸9PO ¢“άg­Í+WA;tÊ òáI NhºÙ›NºR‘u‘5+º? ÇYZC„Œ/f©é°"÷¬ §¨“@€Hø¸”Õ™ÅÍ€‡ð¦¨Cè|´œ!Û¶ª§*j¢ FUÒ3ª)E%!ÁNDׄ´ƒço1• Áþá‰pþ…›½ú·ûO÷j¯GWcóv»ðö¸ùú°öÆZ»µòb¯z»]þþtçÉv9G: ÂìÙªp=#uØR>\÷Ò¤ÄÃŽk)¯ŽÛH稣JìNS™ÔD“óWµxÊ;‡â%} ½²æI‡Ö¬þ¸´—M¸$:HuNš0@¡B$¯&%Úguâ°~ E#Õ® °G‘®Á*P•\:§Dt!ˆÚ&æ_@­Ø®À$\Ŧ\ÀËdFOó|HU†NH|Dc9Ë6ÔD£$˜Z*#Fó*Ì+®fÒõ•ÕLªaRÕLº’I”4Ô0d«@ ëRI‹ÏVr€¡s¾V5]ÈÆ‡m¥UbêyªW“Èö ¯òA]ŒdhyoF»ój¤ Ç» ÙÔ’y=•cpŽJÓ„àŸö^áŸ6ã“€êQG7õ˜*øéô“¶±5O¹Ðåf)(Bäð½Ym’¾œ¯h¨†^MèVQu?"¨¨a?â‰ñ”“£\†ȉœŸc}P‡ °´OÂ’ \HW<ãÿñµxN‹V ¤&úq9ÀÆ*IÚQȦ³Zœ¥¼ŠÉéI3“Êl¤pV >]›µî4í¶éRØÐbàö¢RÑuŽpáýBy›êN7s¹[½Ù¯rÛ•'‡­'‡³­Êþ p²YÛé‡ÌÙ^·Q”*y¾Z”Š&_0ùjYvó“^±^sJjw\¹>¼¸ÙúåÓõ/Ÿ¯~ýtù˧Ë_?]L›z€=—¿}¼úýÓÕ·wà?ÚXþ «ÁçòË´æÏf+ŽÅ9V¿•§>Yl3Élõ‹]ýˆèÙIçÈÑÑw»úþt£.0ϸ*îµõËÍÊõNíÅQ÷Ó“]Ï“:Ü×[ߟüãõé¯Ï¿Üì~}¼ûÛ‹£»Í—{Í»uÈ›£Îûó!äíYÿv·v9.\NŠ×;•›ÝêÓÃ&°çz·Š÷u:)̶¾ÝÒ¦sµr‡CsT—öúÆv7z9Ù*ŸîTö†æîÈlUÙnKÜÛE€ äì´UÜ“Ž ðs4)ìsã†TÖA ¨¥ã=…Oº[GmmÒ8e¶úÆÉnug˜ƒí³34· 㮚"éÔI‹š¢¯aÆ6[ì¸I޵q‹­‘XˉÞAn™‰Šl›‰z.)‘…!'ÃËE5†|«hí…È5`íT.kiˆázž(q:¹ªr¨·ƒ ëHO8á¦#+ºhW | çîTYÙ§ÅDÊÅ¥íÍ"]5 TY¨,^6@qQmÚŒÚ á\j£¤År¸ †L‹j$Ã{­~«äî~Ø\0ÇÊ :=šå}U#‘òÏe9H'à‡­¨”3ÑÎV>Ý«Ð%ÈÆ–Êž}÷¸¼êÉu*6ß*X÷(¦WMÉ£1öV!1ª³½2¡¤×¸Ø™Ëb|E'ëöä8w+oç’*±¡6!¶wý$Æ×øèjÂõ€,ûW~â.n§Âkdp… ¯F]A8 ï|»@M»RþeÿÚ½¸{QJûƒÖ˜áŸ!\IYs“çƒö9ßú8'åwĽŽÅ{ÞU„̯?ú!°W³¬56cýï³i;Tt#âz”ð/H¤›ÀÃçüt|•Š-Ï$x”/l ?Ä}‹dìê„ Û?\gbΰs´wî‡âÞUÛüNkQÁ{ës?ìsDØwcþ¿ýq8–~t®üäÛ°X%æ] þcžŠØ£î¥ ýQ:¸tÌ!€í!sÅ}+TÔ æ :æXÈ(á_Ã…+~p®üõ,ZsÛ]@;áŽû£^‹y¢ÖÚ«QïrVs³LQ ¿íQ*¸õ.§Cqÿ*Ý«Öæ~ל&ĶGe2ár¬ýDÄ ŸíQó˜:¡Kqˆ©y0°uþG‹\ OvÛ99)’Ö. €G¢écÒUЉlX¢KQŽÄ Ì3Ìš ±&sYÍ>ah1Di¿ÄÙ´Gá(¿@û kâØðÚ™ò ªPÄ üH@cÃu“3•µ^“ÕmµO‡¾X[y%Õ*Jpë&Û-òy`-ȱž4µ¢’ÓnٚصÀÀû-¨iDðÐ1G|Ú¡v>Ð(Á»«QßL¿8l4¥ã¶r94€:/÷ë/öë_n&û5áÅAãŸoO6ˬY6)æl`ìÖ„';pÎI/Ó¨óéfw·!í4ä‹ÍâÙ¤h0^9åàcëý2ÏÅm=Àd†à’.~Êo(‰.Ä ÊÃpƒ ; ë<Ôe4''Šzºžg`:)¬Ï£ºÒø@-O·Ê,*0O§Æ£nAbµ`˜*êÃL-ÏüIJ!…AƒKRŒÁVSà˜"Åó- é>SKJ²b²Eè”…J&ma™ôäåX«Àà°šM—µd»Èæå(—°©´µv·)‡Ë™dÕLër°^¤›e¦QDÙ˜¢ª¤ò¡Dh…I;Ú]ÌĸAK*À6"£ÄM= Ü‚€41 Q…`½ÌV‹Œ"Á†\³Zï…݉¹9ÌTòi‰us¤# ‚â|éØšl5éøg­:<Þ,áذ™!U ¯˜d£Œú–Dªó>u:l2§„ 9Ìv`X¥Èˆ| ´ñ¬ÂšpƒyÒË’¾)8ù‘T1GeäÊVA=o¢Ìa¤‰ Üø ê“£Y%¦+1CKªR8£Æ %ZÐêt-¸ nVp¸SYeµ¤„7Â3²e)àu«â¤¥L  §GÍÓ‰ùúrø=™”†…‹½Öv׬L†ÂéÖõÃv·‘ÉLVM±¿Õ¨¤ªÉ]ž ûüô—O7ÿüþô÷¯gfþèÌúxa1ÏtϬ%gÚsnÉŸXŸßX­4 @øÄ:|7=´Úv,bùúg³ÌwkÔñÕŸÝ^çœF˜¶ÞœM1æ|8£š?ðéÃå/Öp ðœÏB¾Þ}zs ù:ëA›&b•Óšê~þ; íãõo.w½9ýöêèãã÷W›ï.ƯÏG/Oû/Oú¯/Fw7[o.'ïà^Ožžt/wkÀÅS`Ƥ4©Ë`ž† 9ï®6ß_oÝîµ^vŸî·^õ>Þl{ ¿¾8þ?ï.þýîòŸ/Ž>_N€=·›•ç»õׇ헇ío·»ïÎÏš×›¥ãnæ|œzÐ|yÒ}s1F‚n¶á¹=ì\lY=\û=c«©ZèÕÉìv3ûC²ÙÖzÖ&¡Ìö(w´WÝß*l—Nök‡[¥IOÇbÍáBÝ2ÌíÌAC ª&Y5ñ¿³½º ÎÙìe'Ó±=Ýš°ÕÏ‚v†-kg®Ýqn{ˆ?½ÙÕvûúvOí‰q‹ÛíKÝrjÒæ ÍóŠ—K.‚»]L§ÉM|^K„S¡Ü¦RØ“¶PG^ñ÷kì¨É·‹4, ²žN…–yÂQÈÄL5¦6à'¢+d|MÙ¡¿ã'&µI¤að.3ñVÙZü¹”IRH¢\øÁ9!(’ }?­pÚÀFFµVТ½*×-3YÞ›—ƒE€ïÓYOA ·‹dͰð¬a¦˜á<*íÓ!û|rûö#_E€G&í×YwI‹”€G¥ãªçÓ•œlj¹[cˆè¼)ûqˆ0i “–XËÅb£–‰¬¨ÃÇ—‹r  î’ƒYÖM‰É5´ë´“‰,þ‡Bb-öÍ1áå˜ó>Xd« ÷<sð '\e£6&²uÍ¥|KÞÕ! ÏRÚ¿–ò­’![–E] ?þ¸1ÿ£5|eý¾wýAĽ÷®û7¶EûÂ=xÂŽÏ꣠}1´ÓQWØeõY;ø—þ¥¨gÞ½úãêæSæ’ÁyÛâ!×Odt1š“i—kåÇTpÀ>#áZ×òîÕŸ LÙ¸K"ü @ w0|ëÒ!;ʳò0³” lìóz×zÖí (aȹ…¬5yÖ¾UûâžÕ{I?`ìgõ>`ɽzß¾ð#â#Ä·þ0é_ÃY€àº*/äœóÛp)W2¸œ .…\‚ÎîµƒŽ‡8åY»ç·=t¯ý·ˆùøíA8DÄjr¯Ý#¿ ,4ŠúWܶûaß²kã¾mù'2i/“òøœxÊŸ×I¦cÀL1K×òü¤c’{»$_ôÎ÷»Ã¦Á€€Gd‚VCFhbLâÂÔtÈ 0†ŒÛÊg(‰Ù€dh‡§|!GZãv*&£‹Q*aÍ‘‡>ÍHñôufC‰d6= ÚaRÖèhÐ<(xÌ—Ky$*@ÇDØ&S¼ <¨YÛHQ'§ÓçCÖä2ÅH-KBvûf=Gg¸P:ò³ 'Ÿöb|6ædÈ!;ÒGG7.v¯.FÏŽ;5%zØQ/†¹§Û壦|»]úx9´Úvn·o—o¶Ê_oÁS“Bžº¾Þ,LòrœßkHÇ]äóâ°ùõvûÝyÿv¯6(ÐÇãÙq÷xhf`Ý'l ƒÌ‰a ~– T Ò )1-‰W¤¸#à%n–ŒØ¨˜]¦ý:Ѹа©›*ºJŽ„kÍ®¢=lÊžS£3ê±Ua‹ÙdV M'›§§”MWrT1›–h—v‰”—%ÜF†4 Z’âÙ ©ki Ú\Š— ‚™¥¨´'µA¹çt/·œãê±][®™çjUÑÓ2é&ë%®Ò>T‰ “*i TŒ.°Ý˶Ël£D×Kªß~CN·;4µQ'Ûªˆ)Ú®J¦ž˜¶Éø[U®]9ÒJá#2öTHzª’§DÖ׬ò­š Ã^#݆Ԩ2µ5ê*ãžÚmE#àaÒv«/I‰Q<ŠÓUPDÀÀŸ¨®ÄÉ„=^áH—.¤ý ãUXD»Ôéô4.íÔ¥0²‹†–YÊ“QãŠÙRãðÁ>üøà£þe|ÉÅ ‰øœtõf™-ç’æ‚:¶v5OYÃx¤rd)/ÒáŸÄúq/ˆšxF RIðPD.dA¦œ"ƒ8¬!ǨØì÷^™ÕÄn‘<doö«·‡Àžâõ~ãö¨s0ÈŸlVO6kÃr§, ›YØ#G;­­AiÐÎÕŠR­ Û&dw\==èÞ½8þøúä÷¯·ÿøöäÛÝé·éäô)óÌÌÌçb†:ßf2mÌX½Nï/~ût ùüæøãËÃYgÖŸ½Tg¿}¾†ÌbZoO¦}RíÌü —ß?ß|»›µÛX}a³4-à¹;Ÿ]5ËúpBLÀÏÇ˯V‡ÚÔ?åŸýþùÚ*ðº~¹»ø†¤Þ~þÏ—Ç¿¾=ÿòüàÃíîëËñÓã.äåùÆË«ËñÛëÍw·!¯¯&·Ç ŸúóÓþãƒÖ“ýæaוùÃ^îÅqïóíÞ÷g‡¯OwêÀž7'ƒwg£W[??;øåvÿ_/ÿ}wù¯W'_¯·îNú/ö›×£Âóýæëãîç›-ÈË£ÎÓ½:àçÙAó|X¸—Ÿt>\m¦žuŸvß^Œ/7+G}ó gì¶õ­†²ÛÍ‚‚†àÛnæh»²?)n r‡Û•£Ý<ÃŽÞ¬ð‡;ÕÝIqÔÕø7ÔX«Êã·Ú˜;Ãü¤›Ýê¸þqGC%Ð(R½†x°UíX‹V™nµ¶ÃhI½ Ý,$6³×G•q“Ýls½*±6+$ºÖ.rñV!½ÕQÚUPcãcbëDhFM¿*äåŸZÛìÈ'Û…N™*¨Qüþ¨¾t.æ)è±v•“Y/ˆE´Ö’²ÓÉ •CMìY”¦;yœ?‘±Õ†µW …S 륦\TÌ$š%F©Ð\@̇ši5½"5…q+”«S¤ê¹TÍHšR@JÛ@>ûC£¬Çz³Ù–+™¸B9Û’-øÖ~jäÒ|rB†£î{85í”gNM`[ËÆW˜áAÌ{Œ.üöù¼j&èØ›X‘ÒDhŽŽ.òÉÕ„÷žF;r¼·W$f\ãò¢?øÄÄj wxwÿ=íHøçØÈ2XHy%Üü«?Eì—’v>ns/ÿÄÆìž°ãa»ÄÂÄv/„lÙ¨#áYö¯ÝÛçÒþõ‡?yWîEœ‹ÞÕ¿‡] Q÷¢sù§9«S)áÛ;—Ç#ÐÎܽ{¶•{±Àª&ƳŠ5®¡¤$6”ºUÐN7oÊ©ƒIý÷ÏÏ/Ýš–a{œ €yD`{6dõm)IŽ €L4!6]ŸÇZ¥š ¸€¨€<=D.p‘KÅdÙ´7= Þ˜vlº ì –•Õ< B|\HL'¿{lpk‡›up> `€ûÅ#"£vÜ2N£‚Y1æ_a‚y9ÖÈQ9!¢ÞzŽº:hŒJãr%K i “ŸM⽇€plÒM'œHx,Þ½¾ùë»Ë“Qô>z²Wº|¸]òƒæf«ôò°õñj|9.\Œò/Ú¯ŽÛÏö-õ–š‘Ú©‰ðìÔ„I™½šÞ_Žád`ì4¤Ý–ÜÎW;Õ–I&=QËmרðZ*°dˆ1…F‘\éð:ÊàÈLÀæ<éÈ›r),n0†bçÕdNŽ™J|\<Þ©µÊ\»Âë|@åüž¼í dºgçY1b* \U3™jŽ®d³"©Rœa‚²œ(xAˆr\Xà"euR““5]4y3K+^D´frÝŠ\Ô°ÙÔ“’)_-GÕLjÔÔJ™TNŠä¤0h§_ëyªSrZ HÓ(sÍŠz¶ …wëj¿© ÛÙvÍZE¤l,aG®K‘¬š¬–#¡ˆa3›*å©J‘Ñ•™Ú¨W˜j™ÊêáfãYG£Ba—)A3³Öbùl"#GjEvØË±¤—¥üV9\îË(iZc‰I̬­ëÖ§aK¢éV…nMªœB–ðÄ‚+à. “v“I>WÀ¹Ì†KS6:Àöàùg„žö¨¥î ²Í"…Úo¡UæM=©Š!†tç2)ÁB&ŸÄò°g­¦žˆ!‡ á2Ô„ÈW¹LÚÐS22TjŒˆ¬5‹ÜÙNu\—†Uîd”»Þ«¾<ëÍLJ­çÃãqq˜?Û©íÔŸ ›EaÜ6ðÃï´·›»£ÚΨZ/J£v–K·®o÷ ‡íß¾<ùïß^üòé|òë§ËïV åE||qðmÆ[Är6‹ö嬄ÿbuo]~}{6;;ƒœï)Yg§Y|bÒ«ÍçlV€¿²þöþÀóåîì/ù {ÿG!þϾÿÉf¿Z­=—Öx¤»‹Ÿßž}~q¼yy1šqλ›í·V;Ï&Xæ @èÕù,ôô¨ûx¿u½×€<ÞkB^Ÿ^ž ^ Þœ@>/Žzp?\N>_oÿüdï÷ç‡ÿ|uòßoÏÿýöâ·ç‡Ÿ®&¯Û—£ÂÕ¸øárüë‹Ã×›/Z·;µg{­³žyÜ6NºæÍ¤úâ ût¯õꨔ‘àË£ÞÍNý¸oî4Ô­š¼YWO·kãv¦W“Mõoö 5hëõ2·3)öFãÂd”ö³Û[¥Ñ0W¯ò£nfw˜?˜”jyjдÖ=Û¯oö²0€ àœíAæ`3?jË"Ù*ÓÛ]H0¨±E-4nñ;=yw ïöåQƒꀂLÉ[ÉF Á«P0[¼&þ&“’ð;$ì°­µIÅp¯Æ›JxÜc‹‰¯]¢¬%Ad&P/æR½†lêqCàŒæ%Ϋ~üÃxÈÄ:•ÚHF–%Ö¤!"+àxؤMa}àÔZ¨ÁJYkãkÞ„‘B%6ká©æR%=^PÂ†à‡ bÙígŠj´Ó.R@ Øš)f$q*'dÒæuœK÷¸ÄZÒÿˆ‰­xRù¢ÝéerBPHÙ$ÂNEVÀBéЗZ¯ÀzJ­¢ß}þ{À¡„ï$ú+zÔà¨É¯.7÷:™F&ñËËÃI‰yºW»š'ýíYïbdî7äOדWãÇÛ•7§½»‹áÇëÉI/;)±Rd¥.GNÆÕ¤¸Y涪<â@vÒQ?{{Ð0ù@#“ì•Ø¼ÊñÁ^…Ó.!åTè>°Txˆl$CëñÀ —ö ¸/Â+[Tæ)ŸD4€·Ñ* [4.x¾ß6UÔ ªp°Y:?hÀƒºõ>õZžn–8HAOåµd»,vªR=Ï6K^™"ÇU)‘ÑI ˜çKy‘jJJ“à3Ë”LÄd²*Ñ*+Í‚PÒɺÉã1N;SÂÀÔx¦ÓØþ©N…?Û«LŠûã4»*¬¡8\@ₚ)›LFŽw £VàØ44~|ÐÖJ¹´®FÁ@<š)šd)OšF²Ó’1`ñr‘ð ûj½Je´P³ÊlsÛ£\³Êö[ʸ—…vhV…fMA:†NPÖ¼ª nJ“’ðàPSE‹÷øb&MÑ­‰0«Ïšƒ†2îdñ`M-9êÀŽj`¹‹I¹t16{ y$"öœ”Ä»øu«b%KT²©A]5¤ƒ‘™“ÃE=>í[dZ5Q }@,†ôlø”Uã…lªUµv»ÆQø ÈðX€C"Ô•¸È%6HÆíá.gÒ“–~¶]9Û*oß?ÙüøtçÉaëæ y½ß¸Ø©Þ´.wëÃÂѤ\λÃâáfmgPÚî—öÇõq;_xÒ-ŽÚæñn§6+w/¿OØL™PaÒ±ð0¥—¿Xâ?eÚ{uùóž2ɬí¬r÷|ÿËC Ê?¾<ùõÓõ¯¯¿ÿÙ3Ÿ^ÿÅHßþ<ûyÚXôGâÓpı(èÝéÇWGß~w:£x>½9FàÏãOà± ?mð™%> ùúæôóË£ÏÞßîxÞZ¨3yseuo}¸ÝýôìàíÕæÿȵ%w×[‹Iår³úd·i¡ÎéØóîÂÕcÍO?}ºœ@¾\oýòìà÷—Ç¿¿8ú÷ÝÅ—›-@γ½ÆÓÝúíNýíiÿç§ûŸ.·^ö^îwoÖ®Få›qåz\yqйÝF´Ö­5Úê8{s:¼Ýk {=/q»Ÿß•®ûp‹Y²ÓPÏŽ»{;µÑЬVH«­A†ƒ\­ÀT "¯%2bxo\8Þ©‚vò RG%—wÔÝ‘±5Ètë|§Æmõ´£q®•ONû³„˃òfGä€v6Û|«˜¬Ñ’ÖùqKªfÓÁfak÷ç”Seü*Tð1³ºÕoiC ´+¨‚ˆ ’Óá”i§Ìù¶*Û£|Ñ2 :e礢+LÚáH§@{ˆø:Œ –pò$ìM/ØŒIÚˆèJN‰Àv(èñR&YÍekf]É1TÐÈ7Ãû³¼¯žKuÊL¯ÂŽ"à‡O®ƒj4ÆSØ)¾ ?ð‚»ÿĽâÞ‡ˆoJ!„Œ›R¯Â©´[&éà[›n/î GÂ÷(œ#"KñÀÃT*þ~Èq?`»=e5ZCbbCJmСňã>µ¦“g7ŸXÕiwQ‰¤ó áb£ëq÷ÔoÕ{–õ×2)æøÝHù—ƒ¶û!ûäo’ð®ù¬RaGpã‘gù~³ÂF]lÌô­âÞu ˜‡Š8­õצ«Ó¬ÜŸNÄÞ»¬æÏê\*èŒ{m©àlC(ÜtïK*#„ ) StÚHbãR°%“؈¸Æýóqÿ\2´rÝ÷Úî'ƒ‹0WKz’‰ÛˆðŠB{EÒq?JWØ„3î[Dšð eõ-9—ï{Ö¢`žµ‡îÕLZØãZ¹o[¸o±P`Q÷*¨ ÌCEÜaçòêƒû¸£˜g=áÛ˜õÄ!‚ŽÔ—îã0âZ&"àŠgý~"°JÇ"éOÐù ì~p<ðÙîǬí¥ÖɈúÛ,ÝXísà\õ,á<1ßJؽè^»/sðLÄ¿„w󀜲ÉIl€¡‰qŒ4¥ÀLFJ”³ø¡ØZŽ5 ÀHšL]R³h9 XBd™H\ÄPÒtÒCÆDÌ©°Ô·*ELCI¨fœaÓ>DSùXÔ¿ñ-#þôl˜´z¸\LÚ#³!‘ÄÁ!Óî-\˜SÓH‡O{é¸S¦‚*NÖ€:Ö}T '§L%-~‹Ç¸H)Còj Ñ0é4—AeùP-G êj¯ªpÈ‚t«J%Çœ‚ÊGÁZDĦ²!k,Pl£ž%»¶ªÄï.G×[¥__~º»Uá^woU&uw9~{>°VsY|qØÚ­K8„[æû-õrRílV¸çMÑÙ¦œüöjÔ+Ñ£*7iH£ª(&íýªhJ‘‚š0Ę5l,ín¥NKòaŽðá!àiH”´Ã$]é 8J”o\j•ødpY¦¼Uƒ,é©zžº:j_ì7Aƒ†¼3ȵJP.b–3ˆ“n¸ªATT.\-‰¥ìÑ”$dz:‘7h]Mir*‡w'¥X*¤ˆ`6opf†1U2+&rr² ‘¦šŠ( æÃoem!…[enØB5kŽ;ÚÞØl¡b9"0^è}COJ\H“âÛÃr¯¡×òŒj % ZÃŒwÁ$ŒLRC:zR‘"Y=Ñn*ͺhæRÕ S.‚|â¹lŒcy3^¯ÑŠä•8Ïî¤pvØ´fã™Q'Ói(9=QßfˆB޹KT9!‹13 FJ™ ®(d¯©‚I¬!r¤UbQ¯nõs}ØÚƒüùaw{Xº9[ºÂZ„Ù(ðy•hĽQµ’et.‚ê=ËG«Y²¨&kÑ-s5#½ÕÑ Z´_Ÿ•Ù¬ŠéÁ#Gk|ÌD¥@²iM éRØTãpû-­lRdêÀ#Ð~…ó¤—ÄhÂ$7Ö®¾™óíòånõõåøÝã­»'[/Îz·Çígg½ÇGíóÝÚá¸Ø)óG“Êþ¸¼Ý+NÚæv¯Ü«e«…g³_zz½óæöàåãoï/¿¼üö 2«üÙ`üÕsñû§›ÿiùû_2¥Ëÿ ·`ÃB ?:›Þ[òû—Ç@šçWã­^ö_ߟ{óÛ»)½¼µç\}°úÂNg´3K §>¾:Æ©?²˜]òö Ñ>½9Ai­ãìÇkK>\}·âü!?ÏäÝäûT~~õ iZ™"‹‹_Þ_ÍäûÛó/¯O?>?¼{²ûîfòþñî‡'û/ÏG¯.Ço¦]]ï#pÇÚÀôfûö ýd¿}³Ó¸ÚªÁ}~ص°çtø|¿õæ´ÿñròþrüþ|ôäs³õåñö//uò¯·çÿxsúåñΫã.b¾:ì<Ù¬½Þïþz{øýñþ›Ã>°ç¬—¿™T?¯Žúwç“—‡½çwçã÷@£³ñÓãáÅn{¯_Üê˜ûãJ·¦–³ÔþfmØ1†½\§¥—Ë|½®”*‚¢% &ŸIUs$¨ðù~ãp³4j©­2Ó*1“ŽÕä8n+Ö.Mqg9ß«ªé–ÉÝvyP6Ù’N›wJš?/£U(lwõšILÁž È´/+E 9F'l<á‚¥rž²¯ä›yS!ªSXåLڤߚù, –P½Ìæ21–rh2ÀÞÃ’ÎéFvêr_~F ãïÓù@:ãF3VG<ðø©ÁäɦP±X Pׄ¢Ë ^™­Á”£ÝÆcðþ ë…T3‰A…ë–èŠoäÒBr -–õX†ó*”s?8dãëí"ÓÌÓtdUL9Ä4NmÈ„‹O:¨ÈªµofÚñ< cË|ÊFF–*z”Cí2©”[JÙØèª)”´SL¬Ëi›Î¸˜è²˜Z¯g“U=QRbãºR”ãrÚUR€:¼ÂÆP˜¥¢#‚Ë ïBÐö °ñ æZŒºü뼫Ž1å‡;õÏGœ TØ$€‚`Ç¢D³|\¡ƒÖ0ZÂWÒ)… =€CþE÷ÊÀm‘Š:À'99ž“⪵O´-î[¢c6 E<ó~ÛÂê;°6×»çÈÈšgíA*´HÅV!åΊ!0m*´œð/B§àµÆ¬µB:´š­3 O:ä”ÈHÔ³f[xà]ŸKøAG.ßèËNǼq߆߶p,†\Ë‹s\!ç’c\dµJ­?‚ d/!)¤‚¶¢FϬNû‚wã¡cù0†Mº“ÁÕò ¯%Ë1ßbij ­CGC˨˜+î_y—g;éÐFÐ9‡S³‰ä:Sh«s¤d°  ò0i/•tr@Uˆ)|ÆHL¸ë^§Ëf§_®CNñ„ŸKûÈ88$"YêØŸF^1‡þ¡ NFJ¡Ìظ!Öìu!.@‹Bˆ˜#saD€(|Ì#2A‘†!ÎiiMˆ~pBt)W üx¸£¢NrIg5ïx…´Z7È&Ü@>è£FATh“Zµ–Ô¬1uYV »?*Z… ’Q›ÆG lÊ£°aµ9j]ïÕ¯öj»ÕççƒËýæñfeÜÔ7ë»ýÒ¤™ëUu‹yJJ«¢ÞœM>½¹øöþê_??ÿÇ÷Û_>]}ÿpþóÇó_?[£fcf~ÿü°ñ‡ü“Ìçÿ¥ YÇÖG‹jfýY>»o÷Œ~»ðLûªN­¾ªib~z}ŒÙáLfl3ëóšÉ–¨óé¿=ûdñ’Â… /‹j¾ÿ9ÿ<ß?üð¼³bþúñæ·O-ì¹»²¢½9ûüâøýíþÛëí×—àœ Ї§{Ÿí|ºÿáÉî x> üzûÕéðÉ^ëñnóéAç)øg»öúdðö|xw>z{6ùÀ½»¼Ú„üüìà_ïÎÿûîò÷×'_ow?\MîΆ·[õǓ꫃îÝéøËõî—›Ýç{í›ÍÚ^]»Ýi¾;¿<êßî¶øí^ëbR}q:¾Ùï¾8Û¼9ìü­ ííZ»¦ÔJ|©ÀLªV“ª5Q×S9LBµËü¤£ã—Ç¿7*ÀÊ8Þ©o—ö'&þÁv…í×ù­ž¶Ý×7Ûò¸Æ¶r‰Ž4¬±“–8lòÍbjÜâ:¥TAñ鬽’‰Tx5‡_ 6lë°_¬®%9ªrÁ¢ž²po³Ä€vÚUÆ’ #’.àHúÂWÌR…,¯7YUÅPVr´S•µ­Ë!ñI[:¾A¥ìªõáAéBIÚ¢¾9x€7*ççÓŽnM¬i„ä”謵ÇêÏ¥M)¬’ꑪêñŠ+Êá¦I k‚Îxp¸3y‡³y)”òÏQá%!eí>ÌÄVÁ6¾‘Æâ`h$g#=]òWãý1?ôæŠÊxéØ*rDü’WHWÕTÒ&¥ìƒ ß2I`OQ)¤‹¯Á1ic"kaûà ÈáfvÛÀå㺄ªuIMB”‹;Ãö9ÿÚÈc1°ñHJÞÕ°}Þ»úÐ>ÿ0XO7üë\˧Ý@ËB:À§|DØfM®ñ-gø˜Dd*óNÛXVaÇtI^Gœœ±¶°LV¢ž…ÙzÈa—µ4®¥§lv-ìãþ2º¦q¨ôâÞø"ž9"²Æâ™$­5[`€#ZÈ97]oça:äH‡Ö†îUHÔ»7ä\Žû $³ó­~ìKáÒ!>é¸W¶ùc1ê^I6~kGuxî\~IltÔ”F¸)¼\äü ™­ªpµY~s6Ù#hŸíêëÓÞÍVeRæà™XX·g{_ŸîöMr³Â#ðj³tØÉÜlWàéšÄ¸ÊŸmßÞŒÛyj«¥äˆL¸<õ¥qÖkbNðZʬAà> N_–5lÛZÑš [Æv¿P6¨ACÆÀ®¿ØkÕD§,4‹€=õÆÁ¤tqЪ穂–Øì<„V PÅñí²8hè›]sØÎ–AB$£&4X%<*¨€&'4É`®¤ò—7¬žZY),ž|·ªõ뙜’4¤XÕdJYbÐT'Ýì¸cmÙ3î¨ãŽ2éiÇ;åq7ÓoiÃnÖÚåP ‹|@6ÎéD!K• :ŸI×K\ê>›Ìf¨Ï«e¡ÓÊä²iŽñ‘Šyª\ÿD &QÌ“F&Y¯ñoæRiV…vM´3ã^®]“r¨IÔ ÊZG#àœ®$¬å†„¨5ãÞ ò9ÚȤ+E®R 5!Œºº”µ¦¼Õ¦ÀYÐ’Š\5Y]ˆe$ktYÁ ­5©ä8>x™ áÓêTµVY©æ˜‚–F-Á$ì†5°AÔŧçý˃ú¨­ô8Tƒµ{»¥Ò.†ôB[UмU*-Ùª‰õ2ÒƒÝ*³ƒ¦Òo¨šŒ[¨ƒBst¶¡š¬¹ASÃëëWåaC}q9>æOÆÅçgýÏöÞÜŒŸŸuŸžv.÷ª¯®F·'Ý“IeÒÊ\ì´O&ÓIs·_žt‹ƒ–y°Õ|óüäóÛ˯N~|}òû×'ß­ö™é,òO×V'×ÝÅ/®¾XàñGóˆV«Îå 0þ:œÉ,§ùó›ÓÏV»uøË´Æ¡÷—HíóŸ”™yþ3ý¿’¥ðŸ¹ÿ‘чëowWŸßžz}ùòÔtõËûëïï.ÿ’Ÿ§òË;ðÌÕ÷÷–à’ow 8Kà±RxuúõÍ9"ÿrwh_^Ÿ}|~t÷tÿã³ȇ§ûïo÷î`¿XÝ^[o®6ß^n¾¼óñÉžÕìs½}wµõ⸽U{vÐ~vÐzqÔA ðþjòázón: ðsw1úx½ùýÙÁ?Þœý |õôðíÉs=©>Ým¿¿ÜúxµýåÉþóƒî“ÝæÅ¸r>*?Ùm½<>;ìžOª'ƒÒA¯ðä ÿújëåÅÖã£ÞvÇ„ES1¨rŽì·2íºlf ÓHÃ(˜î¥•kÙv™›t2û“ÂÎ0·ÕËöêâfW6ånílvA;™v…Ή¾z6Ú+¦÷ûZ§˜îVè¾6lrƒÓ.&­­µŒH¿F[»yæÉŒžv‚'JY ~DÊ›Sb:‡ݮ²U39l‹%#®r…ñ"ª¬¬·Zãù°@û9ÊchqU°vJùtÞ@}ÿÀ*ŸbµëFRÑX`1+…eÚKÅ7àéÂÉkñÝQ¾W—áó2þÐT%G(ŒÇZ}ˆ@*Z|»­5Œt¹órËiG†ñ:¼ +’ .ª¤K&2²Ä§6ÈÈr^—õŸ´+¤¢Ñ~6nKú—˜¨ |RÍ’tl…a“vˆÎúEÂEEV¹„­™§`Ë<“aüYÆ?¯š§Å¤]JÛDÂ6íðZCL2¼rÌŽ IÿrÌ3uÍ¥p!øJ¥<\b£™§L)Rɤ‡5µ–¥©° ÙM1™ð®1QWܳ‚C"¸A„Ö >ÊÄ\aç"vò©€B…*÷­Æý«à¨x(A˜öPžµ¹9¸é°O¹kù÷8~ ¯b x$ b ×l^ÕtîÒ2uó)¿DúÓá5&au%+~»6ÉàZÈ5v/¤‚ës!ê n3RAvÇ|6¿})êY‡'v‡]«îµ9ïÈjÙ·±`_šÃaÜo5ø ðQ k%æ]…‹¤‚60—ô1q‘¦{uŽŒ8qèXšKlçò\ؽ¬±Q&áù@N›tì¸AÜxijt.¬p !> ‡pÌ%‚« R˜ LØ”“ˆ®ã»œ(|Œ%üžRŽ…®gÒ>ž €7 Ý5_i¼QFÝ«^ŠÀ““Ó“º!§D*¨°"êÈÊ)àK >—r\!Ërl>KWLWr †’.,˜‘á">Oˆ˜2A¸5ŸP ð t Ðþa;‡²iBL·9GÙ4tGÈÔIx#€ECy•È‚¦È.B,B¾$¨& ?›ôXKñ@וR& È@­ ‘¬ËÌèpr¢^´i_Û ·˜ÔtÌLÚ÷¥RþƒAþpW’öºoéÉ»ËÑÓýæå¤ôüä# óãÂ//>\OïÔ¾?ß?À?¨ÆQõýûÓõ OòjÂg̓¶~9)Þî7{™ËíòÓãö³“Ψ&îõ²“¦"“ªªœ%¹”+æ_âÓV+g­bí‚0„—N{@; ¢vC¶µNnR Üí’0igeÒ«Ò>•ñR$'G7»Ù»çOû ì8­W“¡Óë&Û*‰í²Ô«*PëƒîÔÕ6žI—Á-9&oPÓ¶ÓÌMn:Ü7ªÉi]! 9k÷ízAÄã­l¿¡7Š|AOƒvÊYËÓÖT¬¦4hI@qW™tÕ‘Q/2=°ÐÀì¶õœA*R¬V‘Ë>Ÿ¥¬^N!j½‚’P¯¨ÃË% Ê®^UrB"p‹(Xž-æi K½*UË|³.Õ*XH“£à®,öšúéAgkPÐ¥˜¡&E.¨ˆ\ÊâY?ȧYW[M-£¥s€‡(¸¬†¯ÎŸ•¢(|£Àj– ›ÎIñšÉY»Èñ1|ͪ 4 ¢$å<§YÍŒÍjüª‘Ö71ã·b’£¶Òë”™q p"Y-l‘ÏÀp`­Î,>\-‹ªlèérži”ÅŠ5-© ^M€…Žê½QäL- Ωâ~ ºÔRxÈÇÛ­žÙ«H/¯6Ÿ.wjOOº_^|~¹÷dóîvëÕÕðÙY÷b·z½ßºÜmLjƒòó³íÓ­V¿ž¶óÏ®÷~ÿöòß^~@Þ_üþõñÏÓIèVÇÖçë_¬¥’/§ÝOçÿ_àü?!3?ÿ“|f¨3óÌÐe<Ÿ­&óÙá,©¿À控iüÿ_Ai¿½GšHùf*×?ß]Yœóöä/àX§î®~žÏ—·ŸßÆPðÒé̃À¯ï€^ó/oÎ>ï.·^žžvžì5žì6žu^Ÿ Þœ _ô_÷¦Ë5w_Ÿô!oφ®6??Þùòd÷o.þùîê¿ßß|ºÙ½Ýk?ÞmÂ}{>¹»Ú~q2¸ÞiœÃZÙ¬Âs³ß~rй=ì]lÖ6kjË E`ÏÛÇ»_^¢r€œî6vGÅzÑZ]yÜ3¶ÖÆí2om¼Õ·Pg{`lõ³í ·ÝÏšR»Â4KT«Dšâ¤«ŒšÂÑ@?éÇãì¨Á ü Á—2CòdyW«Øêˆã–гv¸ð_Wò´ÊÀÅ ~UÔüøY"Ó]z#lj}Ô‘ZªQ"ÛVa}Ó¿€™ÀÔ¤hɤ$.Q¢Õ"U2‹çµ4gmäÒ…Hy$zºz6Ý® ký.’×D„Y²RhT—KJÔàüõl •'lFöåHYÁ%½ó Ÿ‚ü(¤K™L9Éð"€§¨FÓÁÅ }"“*²F„VÙ˜]H¡¶ÔM†K:âþ:¾Á$SŽŠiçl"ª˜vÉi—”rBtÚgµÛÄ7øäZ%;](mgbëÀ'.áØæ©ð†5w}^!}­WÖ“ã¦jJáJ&‰%ÂUŸ.ÆÈÐFÔµ˜ò¯xRþµ¤o ´u/¡5SŠkL(mÉñIdPe" &#‘Hd€Š:¹”´ךB•pUsìl€4‚2mxWè€Hú§\lÂ3]£÷ „ó‚Uˆ°‡K˜¸;ÆÍºˆˆ-êY¢ã.!íOÖí‹ó€œ¸omÊ*k© }ÖqƒµÀN>2êz7@8© Ó¹<ø>ÛbÌ»ÿ„Ýk8õ¬%üÖ(£ caÆ<@!¾ykнo œƒ:æÎð ¿mÞ±4t,ùm !×2›D.ŽÈ´T ŸÔtu˜o5´'€z­€ˆ,œ‹»Ráõ‚NÔL–'ÜÂQ^"ºp·}+ PJFNMǧU!&2áDx#Ÿ¡raêT³¢"औeÊY–Ž»K´‘èsš ‘Øèét!|TÚ«p1™ÐF“tÒKÄ\K%<PyÐE !8«ò104PgÖ‰V-õþ#eo³^+ŠÖ j€fÄYÊfMÖ&­î9èÜì´¹)'§?°»A;À!z:b‚hLÒU°Æè xû‰À²ÆÁ<r)7> ôá¬ÈéjCM#ýé4·©HЉ;Ä”ûdR~u1Ésƒòœ rßžíÝlWGEöÕIï|T8˜íLÝ‹£öëÓþ·gûàŸ²ºÙ®À0Ò~KÛkªãwØÉ<Ù«o7¤‹­Òõ~ír·²ßÏ6sÄN7›áüÖØÔݰÄbÐå)Ÿµõ´£P´æÁE YªV8‘®¢ƒßŸŽì=&A;`žïÀÛ˜³”‚Í’mþ?çÏOgK#Ï’y~·mDO·ÛK¶ùiäYFXºK’ùióAàïÿè‡ZâÐOyºÝùp·÷ù~ÿËÞO·{nv¾>üÔyø/ê|´;­HÈÑŠ>ƒj~ÎáÒà³D ÷{Dö?>ìÛì´ÿ8t½õÃÔs6{w6ýp¹ùþ|þþtúîd8íÀ}:ùp>»ÛïÍkÇãpT9ë‹úÝn÷ñÀBÓüCd· ì¹'ýwGãgó?¿Ýî}¹Úþt¹y»×¿ÜjNª œ«Ýîõ^˜ºÙ+Mš…I£òy2ý|¹Ï´.Žk¼]¼Üé|ºÙ¾9°ŽfµI»¸è—/ö{ç{]|Ýæ8ë)»cg¬“ý֛¤S6ù¦–† šÜÂ’çVqa7ò¼_Xt¸­.?må&-‡V©•c†Ò ~«–™uóFÀðÔuRÈùÊ…0Ù„´㳞b> A1çfÓ«V;7îóíÝÐÓt|µ,ÆjV`C™¤+I­r0U¡*`…âwžõR‘×±ðk4.l`päRÎLŒ,ê¼ð4u¦R¤>²* aøí1<ž¾)!å(¤†íì ÊóŽ4¬q[}´3ªç·Qý惠»wé9åùŸBÆ©‰!{íÄ .±–K¬IYÈmCÌr±}€‡˜ñ=¢žßr G"ø´ó=z~•_‘ñŽå @EH¹$ä<éä¨õtàe1ë¥Ã/²10É«°ë—LøK­7®¤C«a_Uód4™’Xɨ|¥AÅ}/’þ×™ÐZÒ¿\ ãyþ'æ~\ÿÕñÛ/Þ•_bžßÈTk¿d˜nÄý´t¡|oBÎßè¨ ä÷¿AMñü†Æì¤«´ð¼ÿR:U.Ÿòà\0Ņ”o|Øxv®:_þq­%ƒ®¿øÖþ' Æïêÿ€="î×î7¿„¯™˜D”l„o퓹Xµ”çÈù(à'K9I¿›B÷j´°‡KyB\Ú—½‰_™eDŽ’Ý((‡Ä†–ƒv˜„;ÝHDÖÈEÉð!/ `Ž{pw¸.$£-•©É)>î œ¿ µ/×›ý‘ÎŽ´¶’hËñ“‰þöÈ:”o¶›Ÿ/gO'ÃQ%{>­nµ Ç# ä³Ý–‚˜#9k‹vaÞw%²ûÏA¯Â‡ç]Y¢=ó®2ë–. ­‡Üò4ñu.í û_Ç‘ÛlXŠihö\Ƨsáb.Ü5£˜”s!|Sh  8³TöBor²VΜît–Ê%œÝj «&[ÅZ™nj9(ЖžkèlËÌ›eºA¶œàÉ 0\WiÕÄvM¬U8CeM=_)3b>ÆÑAPh¿QlT8…ª@,m›ù~S4Õt½’éÔØ^“v„z…l‰5îK?ê—Æ­¢Òj™.+ébµ”;u©e Í*ßmJÕ [.%{Y+eño«^¬|QL€vD>V’3ÙŒ_ÃC,¦ø\¸Y+,£™:²—ÕäT¯! RÑõªší6D\±UËò!YŠÕÍ\IŽÅHYIð¹ v‹|ḊU%YQR½º°9Ò·ÇF³Â¢¸ÆegbE¼]•bJÊG—+ùíº)Hb<›Imù˜\HKRBâÃd¾%L-ùb¿m™ìî¤R±§ÓnO £œ)âåbB. ¨ü³½¶\VRØsüõÝÒÂs²Œ€Ôðï'»7ʶ-­.8Üùx¿·ìi²qhÿ¿&—¸?¯Hpò`ËÒ¶óx´<\âÍx¾½=†|¹?øáó=I)àvlSç`)OÈÄæ¨Ï÷{ŸníñÌçó»ãñíáhñölþp2y8&#|à¢Õs8z6{<ÝZwÖ-c³y41Ž'Õs0Ï^ÿv·ûp0øp6ùd:= †ÛÞÝþàvßúp¾ø ™¼Ûÿr³óñj ´s€Óg50ÏÕ^´ÙU&5~³UDú/æÇ㻃áî°ÒÕ˜ãyãöhp{hí+G3ób§½7VwÇÚ°!,ê¼_µ‹}2Y@wÉ®[5²°9öÞh ã`®ïM´MKš·óÇ3ug$oå­¡Ò3³õrÜ,Eg}qn–ÔPƒ–0lL•s²~»ž1Õ¾n0®$ÊRÄTàœN=;± J! #2¬€ÕY²­®fÉÒâb”мá/›ug3N]KËbX/§ð±ˆ±Ár´,Wß »*‰^ï‘)’í*™’ì©k4Â;&×2ØåÚª  J!bH‘~•Ù²”6 ®œ4‹Ñª•h—aNúŸÓáWqï³BÆmãýZNÎy«r¬UIã\|• ÌÆVî_—k²d’¸+w%ü¯)ßK.åVÅX.é »õoüš ½É§Ü¨` Y?]Õ T‘ñK¯A6r*¹@)ï/æ<‰À³"ëÔy!ãÉȹ0K¹Â®çõ_CŽ_³€–³Ä…[S }tt-^Y®1‹hÙ¨ÚŸ‰¹ˆÒ8 B:PdÂ÷o®W¿.G­¤É`Æ›LÄÉÆý|:H‡á÷‚gÂîçQïo A»žò¿ªH©°û· ó9è"ì~•n¸ßü —ò¯Z6^üŠd<”×ó®ÓïM&ê¢cnÄ ¹^ùVA&+Ak%rò©p"àl¼ # Pصâ[û T¤‰zWã¨wòoø×_†œo–B’õ®‡\oB®×Qï å_ENÁµYiùW\È qÉ@`ãy:¼‘KøÈ„÷à:ÀÉñT¸ª­ t·õ¾J…7âÕDh€ÌõX… P•tŒº}Ú¡L úÝÍ&"ã‘ó6ã'ƒ(ø8KÆ £‰v¬<“…„˜‹†}¯£ðÀFÆ}=à~–xÛ,€FŠ ž.çІ ÷W›·ÇãÃiýxÞ<ÛìŒëû£ÚÕžu{4Þ›;Cýj÷vç?ßoÿýåâóÛ}È¿¾^||ÜóÀÿíÃñ 32xæÝ1\È'Ðˇ³/àŸ· £øá"î2élz"]N˳>ƒmÞ/CìN´£e:Kÿ—÷Ç@Ž`žwˆ|ÿ'ÒouüéÄêBâØ)Ë!M0Ï1×,ÝOˆs·÷åñèûûÓßA8‡ßquÛóíG·Éè‹À˜êñ‚@$¸¤b‡“û=bºÛ½}w2½:\î.7§³ë£áíñ„ôy]mߟNÁB§¤çëñd|½×?ÛjowN磱y<5Ï-ðÏížõþdòùrë/\ñf÷z»{>oàÙ³®‰Ûw2&}d—‹ó/7[Ÿ/w{½ã±q8Ò¯·[ïO†g³Ú¬.«ÜfG9ßjƒè@qç;=«ÊÀBV5ÏÙVýtQ]t„yWlWh€Y‘»U÷åaWj×XCM¶ª4°dÔÎö';µ¡²;,Ï+{õ®ž²jôÖP·óU%Z½è™­QyÐàz5–l¶¢$ÀÀ E×´T£’nt»ÊTËÉŠ+‰Á–AK9/Ò¯ÈÑ<í²dUd]¡ò´GäÂe9/Ÿ¶½Ú¹SäBB.`¨™ªÕ§“*ôŠ£½¹´’ ¿FÙ«ç;f®SeËb¤ßà·Ç•®™«©i"„! r›C‰ƒâ$ÖÓ«f§Ý¬+–rÞlä¥Z·ªÌ Æ © M7žÑ‘•Y¯¼36Ø8™yT•ã\Ú?O»A;šã3î€ÑVóOÄý,èz–‰‘“4 ïU:âHÛKŸ‘±”1ŸÆYK šruÍ|>íŽù~‹ùžÓÔ*“X¥©7Éàó²ê™x)ä|– ¯JÙ\‘%ƒ.Êù8C&X…ó)ÿÚógÉàz–ò¸ß<ƒÞ'CpןÙ¨Ÿ…Ý/(ßÊrA?!:_úÖž/b¶e&ìíxŽWAçë,åî£c^ïÚ3¨Kè»DhE—Shgc®àÆoÀP‡o€ñ·ñÚµúܵúlãÕ³—Ï|ë¿I@>1Ϛ¦b¾•dxIzcþ•xp=â}“O‡Rag<€ÓŸ{WInW~dÕý"ì|s¯"e:ê-²à +ÏžáWbtÌïßxvƒsÖÜ+/†[HFñz"Á•HŒ‰äQà wž ɘ´ù¯–™n­’Ç .ØÏT2`{£ÄlO›[ãú¤ocšm¨ÙwW›P¾Ýj®mäÈò/ZÖPR†JO,uÐ.vL¾MX‘­–2-ƒðà~PP¿.öê"»5aÐ*NúeèbœΩ()³’ö”v=_Q-3ר2f%æ1+é^“ï·„ô$fÞ4rÝvѨ0|>\,P’kÖ…VCìud¸ífqª: º0u¾U—Ër–É@>dÁC»?Ë6 Å B¬VåJrBä#Ýv¡×‘ÊEJ½+ÂU U¢Zµ´3µÊÃ^qÜWº ÞT3`!ÔÛs m[p²3®o5&Ý" gÒ•w'F·Ê;¤çhÜ–§Ýî½YåbJäɦ|.* ©Ÿóñ²”rQ@2›ò”-UR?2Õ­²#<â®4nFm²ø­Õ@æÔ±Þˆ¹p]Ç'È¥Ê麞·ç|IƒF±VfÀ«º’ª–éŠJèr­6£ok̤)õ*¬ÎG¦d–Vúñr«u½Õ¹ÞéÞï[Ÿ®6?Ýî¾½˜¼Ý…w±€j¾=ou§õ»ƒáG¨ì£ Èçzp{4úòxðïÏ—_ß¾¿Ýê|zÜûúþðóûýoŸN¾Ú㣩ˆ„³!q¡˜ÿE:¶šŽ­±I'›Xû9?2¬ðá"ؠù”ôTh#uyמS¾5 B:âá3Q1ƒB‡ÒOG‰4¾Ãžp´ õ¾iD<+þ À€#ê] ¹¯é˜/K(Ÿƒò;®¦Â.¨þ,åFVќߞÔpE™¥ÒAwȱt¬ú7V|+A×FØëp®<ºWb˜-è=O\FE“?wA &#àŠ¦<@÷Êsä9êyM:ΫžÕç!Çóó9ñ†¯CÎשŠk#æ%c˜‘$”zÝ+/]¯_db!6õ9^o¼~Ž[`â>©kåy*²‘Šà¯Q¼@¾¨wá5d¶ª°¹¤/\ÍÄ\©.çÂÕC®—PmL,) T> ¬Š&à %”É X‹¯Bëçó€ûy,°r·VsÉp&г¼>ë%kÀfÝE>^UsĞ㓌:à‘ø8˜ØSQ²’Xh-qDƒ«àŸ^S¶á$Z7 AÌvM‚ÂêÐIO,L’ò1{ë¿‹ÑËRÚ¶ê„òÙðoà§žlÒ X+›òA?à¢fú´¸4Âtª¢^ÌÀ£’%!¾9¬öŦž‡Z7dá\Ú‡pB2"e(éJ1 %b@kh̰-­]Í#<[GKY“ C&‰7<"0a2k>éáíáÓy:ñ¿ÆSæè =},&²–L ‹¹X&ÈÑÑ ågÓa‰qé`.åKG66úÂRѺÚï>œ N7ÍɶÙOÆÃIoÚÌUyÿñ¸|>ÓÇzæ|Zy»×¾Y˜·›æåܸ˜éÿùtôõz– ¿’M9^͇Úrâx\ݳԃ¡ÞÓ²M9³mU¤ë<ß4å`RžpàM:îŽGtŠl•Cnó w1îª =/dƒr>Ö6xUŒÛs‹2­ªhµKSê˜Â¤§Îúêþ¬à9Þjn µ£­–¡$RÚjIºX«0ÃŽæé7¥V•3Uº^a[Õ|Cg—!ð4tÀƒ8ê*Ðך°55‰QEg‹|í5^JI|¨i²U5]Ói›yÝf~lÉ “íçHua6©‚mÊ­ YùšÐï*8,É$°Õ(heºZÉ7L©T¤ºÊ•Z–ˆyGU2¥˜¬›¼RL%Êê)ÝV¡RJËbhjçj¼*ÇõRºÛ+„è2¨c; ~:Ð.üã^q{¢ïÎ  Ú’M=KVœVé­ÍÕ¤øÎÄ\ t«Q¨Wr­z¡ß-÷:eŽ g’„yŠî7)°ÑR!U×y ÎG+rÂ(%ëzÞ/u ¶Rˆµ*ÙÍ6lIU%½$I”çrK²fU¨jlIJ– ÉŠ”lU¸^Uhh ÚhxoK"Ù8%?À¹¥t³’m–éžÁnõJãš`Usd+„Eã|³IæJo¶.7[·ûÖÃéôíÅâ‹mùp½ót¹õx2{<ž½;?o~½;øöpôtq³}µoÝ ÿýåò_Ÿ/žî¶m;Ͻ8ÏÑïOmËÌéO’ùéÿúîä`ãá„üÀóÖæœÇÿC>’¾$rÖòÄy<ú@ÆÏì/ÿ%I½?E„6ùûÜ¥yú²KË¡eàCrbþ×shwý ¬ŸYú<‡ÿÍ$ÏL",-<È {×ÃÁÛëíÛÓÙÕñøòhtºÛ¿ðÀ8¼>?\l>^m=ÚY€'N'÷'ã·çó·g³bù™^nwOÍÓy“¸³ÆÅfûvϺÛ€sÞŸÎ ·»Ö ŸáÓÉôíÙ] Àóx`}<|»ÙúýzëÃéøa¿w6«ÝîöH·ûTÔÄãE èu±ß¿;î Õ¾‘Ý•®vë¨|.÷Ú[ƒrÇ€Ö S«ªe03¥ˆÁ²®ª‰©U‘ƒÁ °jôjûîtzw2ð¼?ßútµûábëáhr³?¼Üí#ÂÍáðpV?Þj}}<üÏ÷ÛodBúÑïŽ?¿;øúáøËÓ±Ýýžù ‚ þ <§ö_ˆ‰ÁGKÿGoÔÓ©ÝùERC:8§?Ýíýmu9úhŸn÷výÃ,³Ä­%®,­@_ÞþÃncóÏÿíùLÀf)Ëüþ¼œmØYZ¢Nì4»Wëþoû™³¿ûx¹u±¸?_\ŸLÀ?¹;›_qxs:½9ߟÍÀ9ÀKÈÓåæ{Û˜öî|þñrëíéìîpt½×¿Úî]ïôA;÷öZ‚dÌóøz§wµÕyؼÇÓ9™‚vžN&O &²†áàãÙìûÍöw»—›Í«­ÖÃáðãÅ`õhÖÕ¥–ÊŒ…y¯tƒ³®f»­Q=×ÓÒÇ3ýb§uAŸ¬OÚbÇÈnË{3£[Ïíoš‹QyÒÛ({ÓÊþLŸu¥v%=·¤‹ƒæþL³LǤkåxÛÈjöî½Ó>*A+R•b‚ˆœh×òx«5%^×™^KÔÑV"Cõ²જ&mœršËºÞ¨UÍdc¸p©(ä#9Ú/‹T­Â’Ùôz®iäZÕ1´Š1»Jà뤕„>É™U‚€jÆ]° 7êØÖѦ·Ž‹ªi9ùŒ»R¯ž¶„a‹ï›Ù¦šPy6ú²­¥urR#…¬WáB™è´’* á¨ïe:ú†K;yÚ­’Í1ßKõƒ^²Ô$Z±ç2od<.åE+›´ví¾­šÊP×™ØZ£PF4EÖCƒŠQòá²±g™9šLŠY.ÝŸ¥\¨õÀZ&â̧‚ô´Cý’F8E½«Aç+hÿ¨xà cÀƒDÐ €ÿ²‰óÅ|$0Iì*®LÄçß ƒ‡AG:ê&%‚kQß«ºÊÑQ_ÜïíD<Ž k÷±ê!À³ô®ûA;/!aÏ*åwDÜëWÞõ—±ÀZÄ¿šMøA;TåàN†]Çkd)âo8²1w&êÌ’%€Vbž5Ê»‘K„–ãv®7ÈRÛxùÒ³úÊ·¾tl8ß¼ô®¿I„}0Ï•Ž¸sÉ Ÿ " d ¬œ/í×É€÷«x`5Ùð;^¢L úz™Š¬ri€“ à "æJœ€Òô LD&ÖÔ…r!-qÉV7øÙ¨ÖªBy&$äHXˆÌdg# …Œ@¦‹|_$ˆ@xÒE!‘Ë“Q§L”E euÔqß°ÚåýÍ6èïZÁ`àŠ¡ÐM´äF%˜Éôê« ì—+E(tVWÒö<é•GØ#åBý¦ø©È)h+‘ ” ”µÂGR†IxÀZÅ<éA !Àr. r V6R´çše“dû0.ä2a6Ìg"ùt(Ÿ² H ¾½=ºØéÔ¤Øf·øýqÜÒV¨ý¡r±]{1¾Þkmu WÛ÷ǃ“qåj³ötl}<~8Üî4¯µÃAyÑä ‰µÓYu×*/Z…ýºg©ƒ ûx<Ùµ*<åÐ…xSe‘í"!]E2nŠÉ2IrÈexj 7Sã®ß%.ªR(.|†d˜7×ål­ÂI9Ü;=j—îÎçÝjþ`ј÷ËÓ®üt·;h­*G¨ÆåQµL¾Û(@P/Áí·Šp-bÆÉA;úexzM ¨ÓÞ”i{¥q_Å_ƒ®²´®4ð€|bµ muÈXåšN·ê\ÃÌ·Åv³ØjH¦‘?HÕ¬d)™c‚J1Õ¨‰ {0°ÈÇ ¨ìLkÓ¾º4Åtk"ÐQ—3(C{¦X¢¢æŠ…”ªÞ8äPä(¼Æx½ËRZ+¢žÏöšàVÛäêj ‚D@•¼–x{ñ&#)£œ­((½œ/yfɵVÊùf‚D»EÚ#e}É૘ÿU<øŠÜQÚ Ú©£ þÀZR.˜µ÷,H†W RÑ‚ꉱq'0&wæR4½3dm*²r¿B} Šú^sièÙ0´sIˆçq§d^'¸’ó­OG×Q|ÆÏ¥|YÊ I¡¹uG½o¢ž€M&êñm@¿o€grÉ@–òƒb¾u&îÏD½Ð㤟ȷ–Ž Kð NÉR>ü‹8ñ€3õ%.ø'äzCè(¸ž mÙ0¤R¤£Þõ c5êuÆ|n÷êkçÊ+¿kÝçZõ»WB¾õ˜ ¢pD½QÏFصö¬P /€T¶:Óo&æöØ×"f:êJGtÔ™ð¯E]«aÇJð3K2„ó ©TØOÇÂ!—#êq%B@Úz<èö®‚åÖ"ž7©ˆ£,¦ \Ú/f#"µ¹Žtx¡U&áÊÆqk«E/ø-y#²þŠœD•ˆ³Pø~Ç+àʹ"ö±¨‹§CaߟãUÔ²ZM†yÛkè}pEIˆò´ÔQÕ8ð  #åã„N4%Û0ÂcjùMÂU™ð(b²]/x0B.¢)´ÈÅJR ÌþÌØ#~ù–)!\/1…<.þá@MÃ%’'bµÕ~»Ü4„j9 }m–³@è­±Ù®òM{Äд阡2ïáÌÒ&}u1ÔçC}Ð*–EJbÐV[ãê°£tL¾˜ °×M5«Ù“‚+Bl–c 6ÑÔ8ä° ¶M'<ÈÞÒê…2Äû)2<2Ï=éUjgTýt³}ºhô+Ù£qå÷Ûͳ¹1mä.6Íí^ñxf\l7fMþp¬^ï4÷úòÓÉð|Výp:úãn›LÔê®·Ý|ôM[IÎ[… îP»Þé4åÄñ´v81eÚ_æ"U9mz®!kM³aŽ gS~”!ŠÀ“φ‘mdµ^áÆ=­˜ ÙéõSèvU°W†$·™Mxð=ÖÊÌí٬ȒÅôv&ÆTäá`gjIY#ßmÎÉÀþYB¬¶Üª À¡éP¯j ´ù¨WFÄ„X1AJ¨ÍP"‘‰¥¶ë|ÉÞ7Á¦™ƒUb³&Ô«‚Z¢KrØ¿‚s€.e%©êXH+e)©H)YJ)Å´/fT%Ûi)ͺ„p «±ˆ_Q™Z5oT]c5‡H ÉÖLaÐ׺mþeLMeäBdU«òˆO»Q[å©Ìnn·ûM\±3«ãý9Ýï/†^EˆŒ‰ TqF‰ÑË,²¤•p•4\¢0†š«£eQÌT”,âÔcu¥‡:°„—o,¯tÓÈwÈd·œ©±½”mV…nS†‹Ó_CˆÆx¶†ÆÎØœ[ê¬_Þê³~iÒ‘·ê´]ìéìÎ@;œTg5SŠÍüáÈ8Öí@W[w§ÓÛÃÑÉfët«s¶Ý»=ßNn÷G¿ß~¹Ýíü ¹Ýý|»÷;4ûíî—ÇÃY¯t>û×—+{àñ‘ <'Ÿm»‡'K{È’U–‡¶%äøöŸ%ðœýròOùœNRÜ÷ïo÷þBs´ \Z–—ûiœùœN—´³4ï€vþžC;壯Kú²#ÿnçêçàŸÿ%[N~Ïò–ý\Èòöán®Íu‡`žd’×!ßÝìÞ/nN¦ë£ñÝéäæx|{<^šzn‡(üëƒÁÍÁàr·±Ó»ÞÜìYçx›mÏÕaž‡ƒÑ£mð¹FÈfûíÁ€ÏáðÓéôÃñøq¿ÿv¯ÿþhøçýÁ—ëíÏ[wûÖá¸vµÛw¾¸Þ·¦meÜ,x5~o\ù×Ç“¿žŽŽfÆN_9k—ÛõÇ“Á»ÓÑÑÜ7…qK˜öŠýZŽlQŒYüÜR€=ºõ¥¹%-ò Å šù¦žîÕsÛT§åaGª–RŠ©ª™NÔÆJ+RT£Ê¨ÞÓlÆ«¨~G&íKEˆ˜—¤xQˆå?YZNxò ÚeAü%°AO§Ž¾0µH;k¹ð ¤QAµÏŒ:ÒÞ¬:lŠÍ ÝÔè~-·c°™teœb„¶Á€vgÆþÂÄÖzÕìö°4jr2ãž¶Å®NsÉõR>˜O9•|xkdàZ»3³Q¡kj¦ßJB„¦Ö–#ÑL^v È* KÕ¥ìKà'‹~€:6,çcLÒŒ¬ñt ˆ&st#q9ŽK+d —qz‘¬yòõvIˆÑ1ÙŠ:æD= a(OÄý µ8 8^çRÊ¿ìIEˆ™´:A)Ïz"èbAÛ†ƒŒyÑâæRhq’!pŽ—òÂ$Cï ¿=Ij•§Ã‰†êÏÄÜ!׊c…ò{âAŸg}ųþ&ìs½ÿFлñ¯S!gÄ·t­úÖ_»Þ¼NE½ñ+äYMF¼  ˆw ¸@Sø|°Afâdïu*´NG\4p(ìM‡½tÔGú×"žLÄçY}ÍÄ#ɰ?êE®B4‰‡¼É°/-KNpKP,™™¦ŠÜ`/q’þ¾Ð*Mګă¯yÚ+qÁ’V¥ˆAÆwE€x\* ð =H$O‡ß½öšô…½Ïåwá.ž7}²d\FÈúðÄ7P h¥²™ G‡$>5Š:\²m5ŠH:¡@&f…‡§ŠÆu%„Rª‚B¶’Ì„€:šŒ—œŒÛÁßm*ž‰ 5YL-Í;h £n/KpŽ‹!@.j×e«]†:Ø›7ð9ˆl°ß”t4Ϲ0Új!.óÑr!Þ&V 6…vG¸À†àoÚË¿ô ”›ÏSÊlOÌýÍÖ¨[B8—ñ&#«xo+r §ã-Ž ó 8”-dâžTÌ ~[¢P ?&”¥Éxïb.&s”ÄDÊÅPCJô«ù—‹·¨èöº·{³yuhÐÓ™q0Ö&•ÛƒÞ¬%l¶ {V©§¦?žO¯·[çsóbQê̬Î?Z>앳^ƒÌÍ£‰~2«Þí÷+¹à¼UÜê•z{8«£…Ž6>X7Åc"ËsÙPÖ^³È(±x¾iÊÍ]ª³ÈSä\â"öàŒœ§RQò=VK,(¨®åP?›Åy_}¼˜oµZvÚW‡m¹m 5íµŠÀ˜ÐņŸ€§%õÛr4â„A·„¿u—®Òütêà¡Ûú Ðòˆ@t”ùÈè·%2.±Œ—„ð´j\§ ÉŠ…°¡Ý”Á$•]vª?Â! S¬›‚,!$]ÂK%gÔR¶ßUu-'ò"À£*4ΪU çà\¸õZA¯p€¢r™1ô¼¦±U=ošBI¡‹Å´(PR!Y7E„„xA ÊÅÄÞfã`»5Tðšáåé5¤ƒ­Î ­L¬Ê°S´K-ÂálM『†Êþk†/yY&£Œzí2^]| ªÈ7+œ^ÎjJeÒoÉõ a›v­`µdT]Ïãä@49£—™VMê4d|#¦–ë×Ħ½伯íNL<©aS/ÉZ¹aC/Mî`ZÝŸè›ýR»œ™µŠ›­óEóÃÙüýÉ :ôáh|4¾Ú·Þžo¾»Øz¹ýî|óáxöÇãñ~¿ýs³óölöázûw{i⹚t”ÓÝî_Ÿ¯lž!ðõéòéoÒX²Çf~‹ÝŸuö3äÛÇ‹ÿÅ9ÿ4õ,ÏE‚Ë4!É0žƒœc›t–†£Ÿ'.#þ;þËÒãÑÏ\-/ýÑÿóéïüüôü™þ·ü3ÙŸ öó”åU>ÚxóS³ÏþÛëÛ3bä¹>êLîÎæw§³û³ù»«íÇ‹Í[bí™>àðbÓ.ÿͧ+”öåÿt±u0ºÙµ~trí î÷·»ýk ½‘€çížõ¸×:}<ãðéxòåjëßïNÿx8¼?^lu®w‰ùnÛÒúúwe¼›VéæÈúz·s¾0,eÏ’O¦ê%4¾Ž¦ÆÞX5øn•©Êq³”èš¹nkdH̬_œö¥aKèš qÚ(Å-qgªwëy«U¨–3F)ŠU±&§ô2mê,Ú, S°'›U%ÝikUN+‘™’’@E²û“ñr,ùW)œU*Äëzn¹¨)êvÔ6VK"‰+é–=ë×êÕùÍ‘fÕÉÚA -­¢V=ß3sM-ðfÒ‘ºd–VH#¦’¨H1 ÐÞ¢FFßÕ¹Ý)é¤[Xò¬[¨£û“Jß$»÷êR,]©HñÅPƒ Z!ëŠ@q0 GÌÆÇ$6ÄÓ~€ÈKÙ›M’Í™›kƒýoˆìv”ðäÒ>*°Z’6JS]fàré`е’O@ÊR„sìuþꤣN6ác~ÐBÔ»²4é0 ÁAÖÒQé0ò®Ú¶bo :Wà.ÇÜ.M:™˜‡Ï„e.™9Raw<è»W“ö˜Þ€cž|:ŠÓãÈ!„–'¨º#^gÔç{^ÇZнù/8‡Œäq®Ä‚@N† &#þgƒËÄ€:±€3ñebd& €yBÄZâIœvÿl¥<"IøtÄÖ‚Øã–ÈIÈ…;¹„v2ÑpÈãˆúÝéX\%DF‘ü6pãlÂKSîDhœƒû!0Þ@?]øVò"„µb´ZŽë%J5¼d-& DZ`#j!Kù“áuÜo:êì1É îpñ¢$`6žŽXMEÊá)¬1‰ :áeR~MaÀ<U‹4ˆ¢ñ6¤¿ pR’F9>©”Hý<¶tà pµth*!ÅYˆøÒà°]—zÍ"´kEUÎjrIIù¸˜‹áB<1>ÄKR~r–ʵk’VLñY(ô@<´ MJ{£•=î•:ºL¥Q%²A·.ÚÓ¥SàP¾ÊÅÈЊIbÒ)Ä'¤?‚F:€"¼ÞÄLTy& ‘Nº8Zâ²½ì8§¢0ð‹¹(ÐŽÌË“î Ûƒ(hÊU`Âl7(dü*ÛAÑ”ßM>ßl}8ŸÜíwNÐ jæ÷zŇÃîÕNs•Ï~w»¯œÎÍóÍz]ŠÝô¾ÞlÏkKyw<|:ÿõ¸?oŠ»Dg¥úýþtüá|VÊzw-ÀSfC³Ž2ë•+dZLÀ]âСĪC‡é8ˆ—"½™² ÁØ[¾’MЀ:lÊ[Órö숢¦C íQíò`8n+W‡Ãí‘~{2îׄÃÍÖܪ j6t®Ûjz®nÂÞŒúêÈR?f…öËVGÿèd؉T@Ò¬viØ…ÆWÆ–¶=«uBK³#K¨â4%wØ“{-©YM=ß¶{£:-y2ªvÛJÍàÅ|lI5Z™Aàh Œ W)³j‰!Yj®TÌÔÍ‚"¥§Õ(‚”À<µ*oùªÎ••Œ(Ä«U¡jív¹\bd¼“B\.Òšš«¢\Ìè:oè<Â+HYË ù îäÀ²ZÅ–ÉŸìõq ¤ï¯¯–Äx³ÊQ&–aµJàIbÞ)±z™E6ˆ‡tie‘=­Ä@úí¾|@z¼ÿÀ)O žy&FjÕ `!¸Ä˜cÐ^hš"üh#ðl'¢^jVÅa[i¨ ˜gaé§;ÝÃEksP™õJÓŽbÕx4ê'm Ìs83í]Ñ•ŽšÝê÷ƒ“IíÃÙâ¯Çãßov߃|Î7ß_m}wúíÝÉ¿?]ýçÛýŸoOÿx<ùϧË?Þ}#«ó~{wôå~ÿÃõÎ_Ÿ.vÆÕãíö__®¿<ÿj÷:}}:ûöñüÓ? çŸÀ³´á|}öíÃ9ÜŸÀó¿øä'Ã,cB–á@‹¥¥hÉ*ŸmÏ']>Ü~ùû*w<ü´ýèúœ/€:Oˆp½s¶€\ŽïNÈè©û“Ùýñôbׂ;ßÎo÷‡ç‹öé¬y¹Õy8-—1¼\´®æ­ûîûƒáãnÿ~»ûédòÿ<~8}ºX|»Ýÿ åpµsµÓ;žÔÏ­‹íÌ;òÁÔ<š×½Òö@ûp9{lÏŒ«Eíd\ÙjPó|¾ÙÜiÓ¶´=(˜ÍR²]e'=eØ–ÈÆmÅè¸+Î,yÜ m“™ôåV•í5„Ù@­ël­ÂlŽÍ^SBýƒïßo·%7É·ÉÚËff %ó°P;•h]eòl’M{Á<8”ø¨VJòÔ!h¡5´9®"ÍImÛ|½’mèL— Ž>µJ‹ ’YX%{ã4\  ­c0ãŽ4h „|tfÚ-îÏM„­-SÎ’Ñ 1DõxlÈ ÷«Ì¨É÷ªl6¶Šøg{])Ø55“Ž‚Ub&á,‹±²ö@½’Í¿âN€ O LHÊ‘Þt9†ör ðr­6hUÔ®ÉІœËùD:ꪖrÐ×É3vš¥\–rq)°—ÏËbÊâ»Zâ“\*°4ÔäS!bÊ»@&1ÿj*ìd“Á¨w $ºâ7åw¥Â>(ëtdådâÚœ"Ã]VcþTÄÃ&BL<”ÂÎ%#ˆt®ÆƒN€S.¥)Æüë!ÏzÔçŠù=±€7òS!_Äï {×c g5ì[‡ñ+Eð&” ûà ºWÃިߙ‰C®u Kœ\"‚kÙ‡Ùð†¬ùƒ3””MfcdÈ•ùé˜/p“AÄ ¦Â€s-âueQЗ¡p_‰ ;ä^õ®­2q/ã °gÅbC™™•Ï„˜¤Ï"O{ 9µœ‡×õ”VŒd“«Šâ³ä_<MJ×Ê ä“ “Iî4åøÖ"¾ Ü#`¦PÂ"kèyR2 vuäèxF)¤í~(¶\¤6¤?DûÛ"r˜l3šà2&‡‹¡KÌ5öl,ÔÛùÀLÂcuT³ÂsY2FL…K Mœ²Iôe£¸\>®".Ý0DþåA[½lMLh"èb´/?€–Ž)@/ã“÷TN»Ê»åãÝ䳿ÙBüF%ÎQ M )a5‹@j™†§^ɉd|°¥£r€²v á wŠÌ Kä®eZ´»ÞÈË)2£¬áƒ¯ l”è½qõöpx{`=žŒÐŒz8ì¿Û9©óêÙÜØîÉû# ´3m dzêÉÜ“oO†ïNF›ã‰qпX4­¿=8’M%Oãw³×y:ŸÖ 1!áXtäv9»?1{¦PSi!Ø ô¯¡2 S6`S~:îA¶‘yŽ,ÆHÖÇ–¡=™°mª¢8: ˆ‰R!Y“RmgËà??.Ú59CöWš“¹ZÃfñ|ßB¥Ý© KOµÚòæ¤6oTó¨ß Àžv]„‡+ìÎÄA`Yå;õB¯‰zLé6Šºœ3ì•Á @#ø¿Ó¬NQmDk*C˱4ëÒddâP–ÒVO##“«"Â;-‡ ³ÐjÈÅB²(&<à"¥Hì?Ýv 1F¶¥( ¿¤Ñó¶I‡SU"å2«€áKŒ(¦4 áy]šÍ2<Þv…)—¹àDçzmðf€6Û[³F³*ôZ²©qÀ”-^æa·‚7/'Þêe_írÅiŒ¼,eÍ G:ÎT6ÏFf7u :Zú!8W#ý¼"¤,Ó5C¼õ‘2ÞÆJ~ЫࢺL7´œÕ¦=ukTÝ›Õ"`žÝ±Ñ«rxvÆú¦¥Îë;#ÜSt³£«'Óúí®õþdúåzçÏÇãO×»ßߟ|{<ùãéìëÃÑãéâëíá>ßü:ýv÷¯§Ó¿žNþýùâû»£ïde›C¼ ‹æ×÷'}¾úü·Ée©ñ?=‚aþ;~Âÿ?bóÏéÏA8?1ƒŒÌ±AÂN䟵¤‹“Ÿ—XšV–ýœpâ2YfìwàÓNj϶™è‡‹DÙÙ€‡\^‡?þz÷óB?Òÿùïò¦~ÒÑÒ¸ôq™ŸÇ£'`ÏýÁ»Û½Ç›‡«íûË­»‹MÈÅáèî|q{¶¸=?^í¼½Ú¹=™ÝO¯Çûó]먳7ø!;ÖÕ®EV/ãQžÍ?žÎÿ¼;ø~»÷ébóûÝþÓéìtָݳ.w:‡S-¯QCÜÛÃJßä϶[¿ß.¾\N,åra>t÷¥»ƒÞÝaog¨ÎºÒöH¶Ä’†ù±:¨–÷¤Å¨¬Ë1]¦Ð|nd™ôÿ—®óìrGÒõÿÿ0;ÝÕ•N)ï(QôÞ“¢¼÷©”Ò{[™Y®ÍÌîÎ}AUU÷ìÝ=‡A¤@Ä`ÉY‡ŒûG3ä1J­º U…¬ù)æ4¹¢,dA\§D. åe³(èf)Nr¡Z¬Ú úáIßAÇÞ‰\.›>稨@qØTÆm­ðM©šhÿ…@Ï/úfÓ«uPáaSî…|;`‚p¾¬ßŸ÷m9UÊüM¢wɈ˜Q´åtÍ*¶=zPãÇ ¢¡9¨qlþ'd Í¢)%nÔÖA8Pó3±¿)lºäf_æBò?äJB¤÷!C7›3Ïj?·ÿ±ó¼¯¶9j/—ø{|ëo|ibº’ß“*iW«x:—Üþ[jlð‹%Q\1–#Óº7Ó¬Þƒsl¹,W2à…ɹ*#–Ó\1¡óEpdtzï§h^ùûÔˆ+™Ü~— ÈOâE“×F~FQ ‚B?÷ÙB¢’‹“»¹}2ªL¢³û•\€„K ‰-…-”²{ÅTlçç¿íoýœKìeã»ÅÔ>•ÙÏÆßã)rÉ_ö·þC$±•Üû¹”MPÙD&¶Ú)ç’,•Mí¾¿Èáb]@õPI<༗‹Ž¥¶~Êìþ¼™K…ÇùÄßÿ­’O“û¸!Û¢2ÐÍSéØ{…)2ù$B)³—‰ý„w(”“dýÀÝÿ ÒÛ•<†+À=PÊ'M9£KI±²Ë•Þóå-ÐN1ýþ®¸ÚÄ%|‰ØÙÊÙ­h6ýÏÅôv.¹…§@’Û ®rv‡,-& ι¬­Wb1”6Ø}Ö·x¢A, Ž£* ŸŽzÒ Õ <€ÂKt^ˆZ>‹7é½–å ®ÚK£•xJù]˜Á¤‹™mÜ]І´ zMk5«ÏÞ¸k:V«* …èïB:°h¤Ï‡Þb4|!tX;¡Œ ¡ÍRLnÊùQÛ²Õ‚…oPHoö)°•¢Ì$o¢!ˆo6—ñ-U'ðt1ZJÅ© ö<&MçwhùdA…I8r~PS®¢¥KN§ÕO·ó·‹ñͲñrÖ¿œúgC{Ù”OÇ^Û.MÒz`kÂ( fïÃùðóí|p-“öÜ.Nû_®g‡¨óz9:hk¸ðãÕô¨o÷<ÖR&›XÜÃQ€ï‘)î@Eb¡bwA¿•@#©ÈÍ—“¥üNävµ‹‡B CZLR“òL‰ìfÊÔ]È÷ëúýÙôín…Ž·ª—Ž&ÕçK<‡ãàbÕ7öúÁ¨Úkè«EãpVÉ€-kxÃ5eÐ1ñª<öÙmh軆]«[W=z‹­yb»¦u:°§Y•;u­ß6ûm\„ ú-}Ø5»MµÓP¡Œ!®8žXÕñ èulÇb[ =ô%œªUåFMCßp4´Rࡃ-_›õ]¾Ý0ÚMÌ%Ò¦^Vå"IS(ÛdÂPÓuûÆ6ÑÓEÀógÍ¿]¸ ¸üß™çåa àùpwøtsàêÜžOnÈÀÖðþbvw>½=?]/"ûÏì?OÇ·Ç£›£áõÑüsw2º;#œÏ[7ëÞýÉðnÝ¿^¶¯ˆ7rãrZ¿?ì|­»×“ðíxðϻïÓßo¿Ý,ÿùpôõzñv6ö¼žÏ¢Iîø»[ª…ºYÔåy´TT¿* oùã~~>²šÒýaãx`MjÂÕAíþ¸Ó ØNÀšSóh„VÈoa¿b)iWÏNû¦g$&æ…ªM·kY_zCÓå<4 Ç(‡¾àZ t PÈgÀùQ³¯à§À¦ÎÒq( Ðk-p8º¸miÅN(SèCÆ= jl¯¡T­²o–š>Û®ò-Ÿ£'¬Ë½ž!^ÊrzÞ3úuÑÓrU£ÐòKL6\Zçã¥ôßunßU³HA~…‰ e²u;[ø%4Š §Ü#l7`êVÁײ“ŽJ]ºj•zWãSlq›)lEsx³ ›eÜ@5ÐmE²q@ŒÎm#ÈLš)M®+…²œ]JtNbR¹øß©4~Ñ Ñ…b§j”2»™ØÏ¥Ì¶!óñ_ °ÁûÍ«Rz„¶Q˜ür,©Ìä÷ 9âyäLmgbï Äæ}rû'ÆÎßÿûåï›YÕ™È[˜Îíg÷ß ‰_J Ø,^Îî—3 Ÿ´XÎÇßÿ½ØA„Dœ¢sñí¿ÿ=±ó.³¿Ú{¿¿õ8Š=’;"AýYj¿ú%³ÿ]Ø/åâ`’!“}òç»éÝ­Ô.î¾Ñ `i·’ÝÏÆÀ]¨Ã{®£3Ûñ_þžÝ%¶T‰Î'î‹ÊdbÛ‰í_p$<ñm¦˜ª’•\Ч2l!v*$ßGâ,ìɯ㟑‚8KÅ4>c)yâø-‘u9¸Ò6WÚAË”Ù8XDD|k“[ñ-2U-z1%ÐI:¿[ÌláÉó©÷áJ‰rn×VË®VBóß.Ò1ŽN"ˆ,a€À"_åRÕAA6$“æ+)œº0¥x·iûþÆåÙÐo+T]zã¥Ò@/¡¨AÇŒ¨º"Êtôʆ£T Ÿ×% Gº#Cib´ÓTd^Íš½†Ö È$¬ªÅ@¶6}PJ^ª$Bß…ÝðœíÖTd€DÆÙÅ0XÏ›D:»ä§&dq!ÙÅ›¬ÒSRØCÌ*—A9j45lãž ¼!Â+²e)ÑÌb2xÇfؘ G&­pi”¥»…¯µe BåxžÍë7«ö绀ç°c\%Uë`žES~½Mêò¬©.»Æ0ÆuÉdãg³ê¿>_"±ëTÖ}ûæ ñåfþåjv2t—]ý/ë‹Y€Èãq÷jQ{<éOJ%õÓé¬v{2Í tŒ-ïJ,Y¿º˜~Oçã¯äJqüÅb%eC\:šþŸ¡E¥˜©R"›ªPàd2Ðìë̬ç]¯‡¿½œ?_Î-!ótAVN6´ƒ7í:£¶9l™è”fo9 A>ÎIÏé5uD@¡Óž:\+úM/yܵM£j³àÖ¨6× Á3F·¡ÚÖdàÀ«5eÜs¦CoÔµ‡ ´Ó®«¦\iÖnÛöýÉ(ô<ßÒРPРç#À`C5Ð"¡-BgU}ѵY@QÕ—€7†Z6Ñ®Èú<% ©…:F‘K¶Å[&çØ‚ª” AÜ0XÓàt­¢©3Š›éº=›±ô4VÏá:-ÔMC}T©€ïZ‚&S†J×mí®~{>"ûO=­ÿùñâú¨× ¥“Eë çõâ÷׿½]}|:ùíÓõÿDˆ^*üž!esÕ†X~ðÌ7¢øqêùôÞ|÷ÕÙÈÙ×—óo¥‘A«³Ôôƒ£"ž!á‡5fcäyû>†õ×øvúQÈŸOôƒ¬ž¿Ûv¾y¢òß~Ïýz>ÜEvžëEdä™=Ý,?Ü­q|¸\<]#ýàî|v{:¹<^­—ëÁŪ~Ø»8ì]®×ëÁÍÑàþdôp øÞ‘}+€=çÓÚC´áÓª{=­=.;¯ÇƒçU÷Ó1ïüã~ýébúz2üýn -ézV?WózßãUq‚/”|Åþ¼ï jDáúçÓáËioÝÑ®fþÍA8ð*gSÿ?_§-¹jä[~eÐ’{ ©r“¡‹ SN«|ÜѲíoøÌ ­ºF+¡¤ô[F¯e4ª2ú“NCƒ|T)G¶úe“8ú‘Ù¹êrèy8z*€À$ñ9ø6„EŒ ×òi¨¢¡‹bÉì’Ãiˆ0lëPjUùh#Ùçt9v'= .Ê¥Ð,zÞWs8|xÃä~’ÊÛÆԭrHG(&~BºÂÄŠI(ûï*¹w<µò Å_<%yâªÝêN©”ú häêùh×Q22UÉo å˜PÞg‹;\i£bå ¨i/íóèWË9ßc-™Õ[NÈlG2ï;Dù©˜z# ÄÕôɈCd›b ÐRHl±…8h§œA ûÈå ÊßËï¿/¥ÈT¬ÄñÝÍÅ~a ‰B’Ozï]’xà¼Ë콿ÿ)·¿OìòqD¶‰=dïg\$ÈÅß®rû]J镊(3–Oì–Òdb0 é•A ˜J>ULíu2±÷( õѸ¼­”l…2¥à-Ÿø…Bõr :ŸÂ1Ãsmƒ˜|šÎ&©Ø)ALOùx´„ò/¹ýŸË‘~’JiPè¯Eál…¬ÌüKj÷gT€+eKÙD*öK6þ>Eæ¤ï“1Ô³”‰UrÐÙ“íü„«ÊY`çŽ!<,õ†bH^ä¿VòÄJme^ò¥ “OnžOÊSÄe:ñN%®Â¹b†LŪÚ|´ƒç¾8§ã®NfpS?»X¬Pû`hš ðÌåÀ3DÀ-HlBû”À"à¡@5ÈL0I,™\&¤iTè­Ð…}›\n*e\èšøX(Ä7åxp ( ¢‹{F=Å <-Ý ¬Ê¬ïOûn`2\)æh%T¸Wסÿ¶C¢ÖV©º'LûÞ´ï£ß?œ6&]¯æݺî•f X ¥ã^0o_ó¦T(Á³h å܆s$ò¤d2~.ùކÛâäC{|<Š×,¦[•Çm«f3ýPn{Üɤº8ó¦6 ¥ëEýaÕ¾™×º6ý|:8›VGÞ´©ÎZÚɬZ3Š‹®ñévq<öºFǦGð°ê\Íj-m=°¾ÜÏ>ßΈ¶î ìçóÑýqW¯Ä=ëì É—vXj‡ß’YZd£U¶—*i¾”ÀÉlF#ï“8ð” ;l9Nå·¨Â6ÙœËèbèKgw, Š’8nÝ@ TjÞµ¦mÑrM—;ž7Ÿ®æÇ‹Öá¤6é¹óa°‡ãêñAk1 f}tu<À©nMju@žø#&]wд›·Ô˜§]Ó»M³×4»×4ìõœÍ€§YS]¨xb(`›ZUÛ îÚàÖ2ÇbAH\qË m“Ö”B5ºm+òO.ÛfÅÐÊ‘y‡)ÀT!§J”©U\[íoªjY¼ëˆ  ðF¨ˆË%‰„Cp·“ÅœkU,â8w,¦^U!ê©9& ÔP©R‘«¤4`‹÷l ØÖh0Þ¸ï¬z »Z ¶ðNzNÕPs‘Ïñ 2 ºB+R92%±¦É+rYUh 0&QŽÅ;&‡çíw]`›¥ÓžÍƒy )Dƒ_d£^+šðÕp…i×mº|¿¦ êz/Tg]í°jUü«iXÓKgãêÇ‹hçùhøÛíêËÕÁÇŸoÿõéú|ÞªjåšN «<ýëóí½¿]ÏÞnæÿx=ýýÃÉ?ÞÎÿùéòxVC™G³úóÍAdŠ9ÿõíêÓ3¨ãüóóÙ_Ì5?>=GîÁ„Cþ—@øä<œnR¾›wξÿü·a£o´óø}Œ ´Cƪ.7¨óåGÉäªÓ·FäõáèõÛ0Ùé±áœnøäcdœyûfð9ýüï%D›ã·‡£ˆõÿY–67úfáyÛä<~¹_ávˆl_#›Ï$>"޳ëgB+0(èælr>Û„Û“ñàçhxs¡‡Y5ºU¶æPƒ¦8èÃÚ9ש¡ …ž"66tÊíPXÍkdÅõ†e§æ ®I; ½–z‚¡GjäœOkJÍêÐ@:>-­„@6V(2Ç!Ñq •ýf43k6@=ÝißîÔDO/ÖÝʸ­[Ê )÷jB`ªF14)WɪÌÞ & ¬ë£öÃù`ÒÖú5Á“¶”B:ùY¦wp´¤œ§—L1+ѱšU¶¥´Îî›Ê°.œ„«±ëª•Ù ¤À[|/3q:OVÔé}†(‡þH¼‘ÙäfL®¤„Rœ-ÆèÜŽD§4¨äј‹Ì“MVÝ…T-gwD:aMe …ãuWf‹û ™Éú9ûïy*¥²:C²82€Ä+¹x)½‡HàŽIžJSiˆû÷Tj—˜P’»ât–xË’»…dD;ñm¤Wò­íré[àËQ •|?µópB¥÷y*—Þ%Ã[¸§hÂ0äÂ\|‡8ùìoe÷È*@|1Æ@G?ë|FaÓ¨< +ßâ˹ÌþV>¹9ã%¤ÀKÂ?¿x¨ô.o#ºd·œy—‹ý,P‰Ô6YodhÉTÓ“È󽟳ûïòQ¤J¾˜‰¥b?Ó…8[J’{ñ÷ µm ò€p0YâÅ´K“ñ¬÷¦D¹zYåÓ2/e [â #"…$ÀlˆÈS CÆÊ™d6†÷³Ç’ùÄ{œ…žXuYEHƒÆ dqn‰á¿–˜¸ýã¹Ô/l9©ŠE°Š­Rx›€Ç7+¥Ò“q Ö³xp °z(Ò7¦›^Ë µëè…\¥P<“ê6-›Ìs'`ΉÒË(AâršD¬xëJZáó<Â)‰%cj³a ÷®¬ç­aËwÝÕ¬ ‘Ú|»ªH•”!æ}²×s©jqd»„AЫU‹÷ íÇÀäT>ïé ”|ÍñT`É%¤s(29M(2[ µ]´#qÙnìD Ç傳äm¾UUë®Psù^]«»Ü¨eŽ‚õ¸ÚtØóyãò y1«_ÎëGwTN†^S§Ž‡.€çþ¨w<öãhìw}îpè¼\MoVí«ƒfÏcÇ¡t{Ð:êÚÐÔFaÞV<Çí‡ãΪg]¶>ߌj’ÆÄû590Ë<ómZ•2*+:%Òiñ JLG5b6•l·š/åw$´a1WÌmUJ± ãËø‚vØü^h±¨&xާµ+è’ë®+äJ2ôì  ŒAŸ‰ãѼy¹î¯¦õ£Yc=k,> g=­ƒVãZÓÑ 4Œù ˜ ªÍªêèýÈ¢”›Ð©ë]2Â¥ŽûÄÔÓ ånS'Ã[]«U7ÚM+B¥êËôšÞj˜ˆG[ˆHÑàëÚœï Í: t$´ˆH®Íž€£*mƒñž@‘ÎX54ÊbÑ69 à§ê+ž#FãJt¨øih`$Ú¶ˆ±—W= ÔÔ¨i­†Í+Ô«*ÌÔßQNàJUOQDJâ ¸žm ºçjÞš‚ƒY}1 G}¯K¬CºïІVql¡Q³Ú-OVhÓ]O1 ð£È´ïÉšþ)ÕCr_¼-À<¨?žZ#¶Ç¬«3 OBh‚-¶îð¾Nãýø}“nÒÁ(hÙìÑÀ»;ì^ÏOëÁ—ëƒÏ—ćló¯/w‹vÃd…Z´í/·ë/·¯×“·‹ß>}¼;øíÃñ×çメ·šÔ~s ú¢l? !`C؆Ü w|‹n´É€SHyº]=Þ>ßálÄEG ŸÇëƒÇ+.wg³›ÓÉõÉ‘ÇËùíÙäf³zóº{uÔ»AdÙ>ÔNÆÕ›%qæóÜ.š—ž¯—³_¯—ÿ|8ùü~»üG©v]®åp³Ž} eN»Î¤¡Ü6>]O_/FŸ=9÷ëÖ¼¥öOw³ã¹× Ù~S˜ôÉPW¥x#`ºuQÈzY݆\Ö'ï6È„f(yVE³¦JyÀ_ÄѳßfÐöª.W#Ž÷RÃëi“­š2êØH´ÌÅ$„6Ø ¨É3ËžQ´ô~Sí5”aK_Žƒv•·åìb`_®ÛMiÜáØkz™Þ¥’ï¸ü»Ð(®Ç^Õ(ÔLêbYGh:´\ÞÖØ(‰u‹²Å´!d|ƒvÕ¢!¤ÇMuP—-ßùª‘5¥åªAaÐÆmU“à«Èiçäåý~ßßø3…½Rf 2-Ä€*›#‚8‡ôýbj qÈe…ÍAÐ ৘z_s$tÈ™Ø;©’©;*øD(e¦@¥ö0•\¢HÐe«˜Ü&ÃIå¬ÊÁ*tvœ³ÿË»äö»rf¿”uÑ(\ÚØv²À8™Í rÈìý’%“Ö‰_.SˆsTŠÎÅq ˆ{*µ_J'4®BÈ'µŸm!஘ÞÔAçË`°rzG.'}µÌvèô;MñÔž+·¿•Ø~—޽gŠéR6‘Ü}—ÚÅ·"fÀØŽPÎÈ•Œ£–É>Ýl*Úœ´óŽJ¾‡ö4Z(Çe&ElbxÅ8WJ‰•œ@g2û¿dâïdZyÿ#3…bj{Ã<ùä/x{,•Õ˜2åj%–Ú-åÞU xá»HÞ<þ¸]üxp<^ЮœI nùø3«r92µD+Ö.phEH’•ÿ!7ù”ÈÚaŠÛd¬¤¸Íu•wŠÙý÷ñÝ÷ K£!úMµŒ·W ÛrtªjžÍvUh„ld¾¹Hìÿø‰ ñ³È "S+CIo×͈ M,ŠLF—)™ÏG3ܳº\âˆÿ Ò[è6mU(ØjùdÙë5ÌòÔÄee6ƒ>¿î‰r´ñ:˜Ç'‰ :¾É9jE‹*—Gcë3¥’«1¾Á%wÞß•è^Î""s9S.QYâôœ«ùò¨ç“ñž\Ä«VʨocæÿàÔ¤çm×<½_vж_ïA&çn×__.ÿø|ÿåå’y6¦›—ó¯/ç8ÙINþO ú“‘¾ñs>ß$~ùðoþ‚=ß ,Ȱ¹Ñ_,BßÃ&åå|9à ™¼>‘#Ò7§íDñ·hdjSì¿›˜¾‡è¾ßÂfŒ,ºüó äÌóúô=<Ôùp¿~¾_=߯o?Ü}|>ûø|Y{޲œË£ÑÅzp¾àx}<¾9_ŸŽoÏÇw“»óÉÃÙ“ëæ¨¾h.{Îé´v-Ôs0«8|¾˜ý~·þïçÿúpöŸÇ ßnŽûÇ=ûõ||{Ø>ì9‡øÊ†Á¬çÍÕI‡8zݶžÎ/£ƒŽ†puP?ì™Ó¦üõiysÒîÖ¸šWêµxgççc{ÔÕ«D˜|–ìÀ¢·Pi¢kUt2>Å!èPCôràpКC²Š]µÙVUÞô[æ°cÏþ¸ç4«øüÕAÛ$P„ ¡4hö9îÚC2ª¥µÉzãr;à'Áź½žË‘ìAXaCò”L7à.›—«¦-&+™÷U½àÊpŽm¯b‹)ƒ‹+•˜£Ð3ë|†J½G¯ï)9¤“@;€9žù«»eì#¼eˆdE2¦°CL:Å=.b²Ûf™Xu*ù½hÀè=D'>4ß|ñ"_J±Å•ÙAí ˆ• D°‰uÄògãï!eª¦œÙ{_JÇØB в±Û@@oì9L>!Wò:_≑‡°M!¹WJÇË™x>¾“Ú}”h€&Í€ˆÀ3dÄjg™B:‹¬=)T´“OlgbïÓ{ï³ûÛ…Ä^6¶“ÞÝóà˜Ù#0€ QšÆ•PWLZb‰Éî²Ù_¡<9ÐöO+É•¤Xù¤QWÎU éÔÞ{Ô,Aç‘ýdKªdÁu¦Xв ^Õ,W­JÃU9hâäUàX¬§W •ð¢òĵWÌìVŠdFv.ù<Æó\1=` oï­œÛéL¯aAp"$™jMçß#@èã­âáK â›.r…ä6™w¶ý~C;øË˜BL&»$d›U®r¡!ž–¹}¹ò.ÈÇÑÈNµ–œ×ÅÌ ãÎFõFUŠ€C,­¢@†òyÄm½b©4ÁŒÖ¼˜4ÔOPñ½‰³€Ë‚Ç^ˆ¦LèÈâL•6ä`F“(”ÂQ (CAQÄM2ÛŸ˜ "*ew Ç3±-ðIñŒ%—ðöд4>Ú icȳƒ$ÎV#GeL¹Ü ´ª-†¶Ôo8•´Õ "–B—s{ãN0lyžÁ t HfáAä˜mÒ¯¢Ú@ "¿œÈ%·ð\¨6™¨^I©b>°9•ÏàÓÀòwl[.\Z)ÇkzyÖ4.懣Ž·‡¡/tÌ»uïhè÷ábÙ\ô,©¼Ó« _ŸÖMPPÝ(õttر®ç¤/êxôù,¸˜W<'cïd|¾[®®@5ʃ¦f)ùrqËTñsx ¶LKtz<øÐ€41”Qè”4¹(p€ïò ¯”É –ÈS·ÅaÓØ/ˆ'ÏàöxxyØ™u¬Ûh:‰)äÀê—¨´Nl† cÖuÁ6‡½³ƒNä~Ð_ÂN Mç‡=Äu°“Ñ ¡²dÜq?:v€§æ†•ƒi}6¬â8úè ‡]»ÛÔ;M½Y7Zu«¾#y¶€c¨ƒ^ÐëxC 6–ËHoÖMÀƒ…~Õçu¥ÝÔë5Å÷x<]à‰ #S¯€^|Gè mél§i»6 ˆé¦™q„0P,ƒmøªQÓ‘â“!3N‘ɤuíA§76Ä>cñ¸{-Mg iÖ-۩ܬ™h¥ÄðØó—“úÁ´6ì:d)E›á*qý€¬-ƒCf“Øs8××mWq\U7ÝàMK4 ÎqäVÓt©…g?:ÌÚø^€pªR…*Ði9ªÞðÕvU'À£3um›‡"Ürt:ô¥6˜ÓÎ&µëEëþ°û´Þ.:·»ÃÞäì¼ÙB~óÌšÖËùâÓõòóíÒº¡Aþþrò·³³E³_WÏ–ü³o “‹×‡ã¯¯W¿ºÅñós„:¯ÂÌÓéÿ<__."Œ9ýnT9#ƒbߨ†\ˆB¾Á"Qâ&ÿ7äxú³üͽ¾%FÀC.‰,<#{Ά@ÞžOßóDœ2œm7·Û„{ÛX¨žÏþz_ž˜‰"àÙÐÎæßã/Úyíðútút·zy8Æ_O@>"z¼^Þ_,nΦWÇã«ãhçúdrChgúp½x¾[>ßFË8ß,ž.¦w'Ç(\-š×‹æýaçaÝ{;8n,<ÿõxüûõï·‡ÿ¸?ü|19¸¿ß¯n—íA ö«Ê°i šæÁ¸¾‡“¶q½î<^¯¦‹ú”,Êm½ÕÐz¾ÞŸu»uÖ·r­zeØ“'Cc20ú-¥Óúmշʺ4êYd‡_«8è¢ –^j„ Y#K*„d™²fW§®uúMcÔ±¦}[¥Æ]‡ «µ­Ðá 9OÜ/«’oу–ÑÁ÷’™Yý¦æ›%Ï( ½Ç«é¬g¹jþpì?^ŽÏ›ŸîÎèy×ëö¸¥öB¡í3Ý*§11 NÕ(ºJüã«9OÉzjM·á ŽR,¥·„â¶Æ‚‚ö,1Õö+U#?2ï›í*'³{¥ì_Ž)lJåÐgÆ!²µ’’™C¼wâètn‡+0bTz«Ü2„[L–³Ä‡£R¶L1^)ì ¦9 ¤‡JRÉïÆ·· ­Tží-±B¤qUØÃ‰§®ÂAJšø“Iå]”hªLFgâT*Ô LÉ7D*½GÜ~H¶x%—JùJ.-Ò‘Î2Å}*½KÈ'½·qÝÉÄÀ<±b™Sù8.¡e ‘Eþll w,Ä·˜|ÜKZTÕqÃT%G›¤Zž ó¹RzÛ’+ ÏHǶŠi€ÊN&ÙvR»¥Ì>î䃨-g¶M)¯ :û‘»øž Ù-9Š@c6Nfô»zì!1™ÔÞVl ¥í”ó1:—ɧ!ÃSÀž](C°&ŽxÏB9Ý T[¥…r.f¹òSÜ6p;1_În‰‘®)‘W$#hq .þ©|hÝkª£žQuJž]…}ÝáÊ[³«Ë)|Íc,•´Iß(¤ÑȨS` 5_ !)¢(×d‘‚ˆnŸøÞõ…AÇ#ã\d~zÍ[a ™Bs'køZБÞo£»VQ,ŠÂQ—(”€ËA8–^‘ÈÒÍ%…Nf„ÑÑ…åé°NbëE·ß´=ž u£$1iR+WÊÆ·ÑÀPΰíÁ§^Þô‚º§œ%&§ E‰É3ÅŒ¥0ž!ÈlÁÑX…+€s.§_È&¶kžÔ”,D[fd“Û…ô6G'qëNÝDù®Á¶jú&âê…ÏB§µNëëyóbÕ tÚ• W«Þ¢çŽjꤡ_Λ'£ê²c÷}a=pïÖÝõÐk˜  k=|5ïÈÙ—ëÙýéðæ¨‹G±ØÄÙ$<ê»Óš\ þù…8Õa¦—‹Oÿn½ù_€çõù?>Øøø´Û:‹,3物çô3aRÒ‘ù‡±esvC#¯‘Ñæ^Qz„"QÀ%„vPìë9ŠÚØ[¢H6cR/$g ¥|ŒJø=Å—èÔæøù»¡é/ÀÝkcáyþÆ<¯ß9ç{ü[ˆì?$øçþfùt·ÞÄA>o'„ðóþèáêàö|vs:½> \Œn/¦×Ï·‡O7K2ðƒãåìåjñx::‡çÓÚÝa÷ÃÉèÓÅ äóz:þr1ÿzµøõúà÷»å8ùÇÃáÕ,|>îßõ]{Ú±z ÝÑË@rôëÊ¢oß÷ß®ç7ë΢­Ы´æÙ¼ú|5œõõVµ2ì*­:»œ{‹±3î™Íï·ÔaÇØ,§,rIK/ LÂЊ­††oaÔwëTóxPÍ Áí6´º'¨BÖ3+mp‘F¼&?“€ jø"àgܳûMâ{9A¤¡ÔœÊ¸m¬gá¬oõB±ísdM°ÓþíI9tF ÙS³Ãºt< B“5¤Fa5ö¦½n—n¥áT²¥¬!d7{L; æA'6혮’ù”ÓÛžš·´AS>YÔ{uÑ’Óº@æê*LR,“MU6½‚A§ ìn(UÒÑb³øIÜèì^ X’ÙJY¾”e+TG`ˆ!Û\î¥ö¶¡kGCZÛè™+yâUÂÓ\1ÃQ™p±…˜A®äÄrZ ’b)UÉÅèÌ. ŠÎ™ ÁAS\ÂQ9’l[P=àV¢ T{˜B’‰œXx*+*ep¶“Òð3WLí—2I*<™d ` Tr*“/§¶u6;k»Uöä‚%d¡êBŽ+îWr{¦Hãî•|ŠJïoVEÆ­Q*½“!)x®ýn]ÃÿùQo+lÒƒŠ§W6t‘Ì+ßfK{ˆ”³ÛùÔ¶PN9:R* ^I@7žåöw‰K6yÀ}™Ék| ‰²/°jó O¬¹¼&¤]ð@þr¥˜TI2Å]¾' Ðã*W0Dü3Û…ä¶o’ÅRêd“Ä[ÞÖ䎊Pø¸ÄîÛzÞÖ º”¡²À§¬¥*T¼ÙuMbŠœÔ'ôd Ž¥BÇ æA†À‘Ž H÷,^‹HÉàÂfh €Áµ›B)®)¶äY‚*R8kDµ@8€ŠvÝÀO”#0ÙVÍB6…§Tp —‹†“„к c}ÐDÑ…}U,9:o©lè¢l¿å5ªY™Š‰|RÌ6ø\j·œO:†ÄÓùdl7¾»Keb"¡‚B.±ËÓ‰IÏ«:ÐPtÏd$²;ª ¤\ùµxØ|ÝWêx|Q…|3 ©j]͇ãÚ|à_®û糯ϧ«QuÖ²Ú? å£Qðx2Z<„O7‹óY½çó=gÑuºUQ vî¯ÏÇ`žÐ(µœÊÛõôåb4 ÅQM\ „‡hrÖÑ8x¾˜\.[W«öÝÉàhÎ{Î媫q©ªIã‹VùŒg0Ñþhißà@tºˆî%g)DšU œÜ¬ª 4õ¨ëâA€…Ýš¾š6B‹í\v†uõjÙ¾=ê¯GÁ¬môÜ“Yc5 דOí&=lYðI £y/'µëÓñÁ¸6ø=†'’ØCµßBOkôëú´ë-†á¼Ìðkú᤽œ4F·jàŸQ×C5@DÍšæG ì€^:- üÐmZ½¶=êù“AЭÀÝIן ª«YÿšJÔê”Zäu ‚4 NÓÓä|Oà’³€f4¨†!20ž+6ë&2˜ v2CŠwl1r®xdtI4TV(]­~jUÝs¤nËs-Iä©zÕòlÙP™A7tƒˆ©€O2B¯ã&Ã@€ ðç¶í~׫¨ÌÁ]L>ðTÏsÉ* HÏS ¨Ý–Ý#Vm<{m·^Õ,âG$ 4ëzà‰d¤ÏbÛu}³iK3Tk_U©ÓQx{Øä\-Ú׋ö‡³éËùìùlözyðx:}>Ÿß_¯—Og³‡“ÉÓÙÝòö¨‡æú|>ùx³œ5ÍëÃîÙ¢ýt1¿9Ý£m¿^þöéúËÛÕ§— ßÈ`ÍÙ—×K„×G€Ä I¹üD¸å<Ê!áA°ÄÅ&?Îþ5 x‹ò|Œ.y#tñíÂÑ…?Ò?EùIüûµ¸ïç×KäÀ|ÞX‡> >H¿ˆü“O~Ø‹Þ"dŠ8àqÁ˜ï ô-Y„"÷樴O/ "2Xöåí(…Ÿ?€ç›?Ï¿‡¿À8çäÃÃñóýÑ31ï!Ž<°çévu湘#Ü].¿»˜?ß®>Ü­Èô®Û è°t¿~D†“ѲïŸNj—óÖëÙìílö´þvsøÛõò¿žNþûåìÇõï÷«<®®õ«EìÚôٺϵ›ªcÓݶ1x³žs8ôï_VŸnmãú ùr1^õm¨TŸî—·'½Q[Ÿö­ÃYu6tZ¡Ðª “®ÕÝåŠdÇÈërz·ßÕ¹œDƒ%vóñ]G.B} Œr`”t>‰%–w !éªT9C¶$í@\HtBaR"ÊxT.Ç—DªRq*½ËÒå qŽ¥sI¾”—*E¤Tr Ž$n.¾kˆÔ¥Br7°$†JC•ó‰Bz¯œ#~,HG±ŽJË•4›ß-Æw¥R u“÷TDçÀT»¤Vıy?⢲Bùibó?pð“û C3ùl!±ªçTòœ“öÄéB6ŸÜÏ&c™Ä^.+çÈr7®ZQYVíA U-¶RØU9pÚž)ýL[rAáIÒù][)AzF†Žæ»QÉ]Ê–ÓøËb5[æ©Ùê*y›‡ðŒ KÅt1²íäÈMyj¿í"7ÉöùRºRH€E5¡ a !^õ¸FÀ‘MJñBvAJ,µWÊí‚Ä9C\R\yw1iu›vʬΠ€s2'Eß zÙP 2´jÈÙ3¾-´j&2û¶Â)¤]PóÛî&?„” íÕ‘-5|DyE( ÇömNdÓ†ú’Èfp•!WÐ|³‰½Bfoµè¡@âÕãò†Ž/«ßqJ…=ɵjöÅñ"tõ|jOd Š@ù0ˆ9‹…"¢¶žÎ³…r!횪¡2Oså\ਧ«ñ¨ë›j¹*U‡×Äô‘fU®ûâ°ck"à!X<™dÔ0-è#vÍ•<ƒ% G根̇ÁÉAçì°w2oÎ{î¬cÚù¢uµ$OŸÍG„ÌeÏ~:}8ŸN&µQ]íU%SÈàK_ôœ“Y­_“Z}¾¨/Âi‹¬•z4òçmýtZ=™†g³Ú¢c®GþÇ»åÕªs{<8žÕNç¶Ov Úö8°Õ²)$¶•J`‰OqTÊÓ¹š+·È8ˆ\sÅNMö˜É€UuH¦˜±¨9^°+ç<¨á²çÞ®{Gã° êðg“^¿”ÌÊzÞèÔTtYPmô‡R§® g£pÜó§\©k£–=n“0jZˆÏz€g€xÛ·ÌáÁ¸1l»óam5ëLÕù¤1è¹­ºÞi™£^äè[%óÙQòb\¶N¨«€LGóör\ŸCœñ¢9®ljï:Jhaͬ×MÇµÈ (Õ¨kt¼ ÓǨéˆ,“w „S 4ÛÆOÁ÷µ°jÖB«ZÕ ÑdZWËà;M¨cê‚g+š\iÖA5`l6U=—÷Ȇd÷RC+!üÓ븞#´êVÕ'à†,¡´ª´S¢;ò¦Á›`!Kò­´˜¾£‚yêUG¥ öë4,[«@whTq ºÅظż5è“Ùú¦Yó%<10*=_ä<Öƒ»UÀót:ùà¹X<Î>\,À9›ãýÉäátüárvw4x<¿^"ÃdѶ/ÚGã0þîtrw>û´A‚§/O'_^ ð |ލòaÏ2Ù° òsÃ*Pùkü5"’çÃ7˜y‹Pçí‰?Gtôñ>}8ßÜg‘s»MiKÁ›hˆmc¢!ãPÏÄv´Á¡o#S‘GÍçÍØy¢³ïsE¾Yi6£cÑYâöÑÎÆèì»sò{N¿‡ž?_Âæ¡ž¾=ÚŸ twôt·ùàø|·~¸:øp»þ€Ä0ÏŠÄq¼9|ü\/Ϧ‹ÎÙ¤ñvqð°>,û¿ß¬<_o¾Þ,ÿx:ú¯×³}<:é_Ìëw'=°J#ä[ðtÚædàN:Äæs³ê}}X£šÔÕEK?GÇ2 ?o7‹û³á¨¥OzÖœŒ5›½¦ºœTçC·ævòÌ’oÛ)K/m³@€žølpÍ:ô½Ý0jU½·NféòdGE²û- õ*š­`@Íi²Ìf|“X$~>h´ëkTÃe§ájä߬»ÐìÐÑ-zöëõâpä#œ,êžšsäTËef]³J¾Vh¸ŒE¶kO{PÕ•Ä+IÆH'‹‰=ð=B ¹ Î÷™ÜWŒM"÷¹²ËçôHª%gл 2…Ëû™{ž±e2;ÂTåˆ d_&k}pT*SHÓY³•\ºœMVòà _Êü(çÀ!…ԞʕTžÊYW*ßÛí@B1TJ¨¤¹RBbÒ¸‹B+«³[,x2(e‰Jè|A xœJ~h¡° ªœÝó |¡”UXJfŠ<•‘è¼Æ•Ë™OÙ2ÅT¾T,ç2T&™OÅqãJ1K3*KåRˆ(|©œÝJ6rtUÆ‘+*F¦ÉïEãnqÜ¢ÜãËY¹R¬äR*CóTËäÓ¨ƒÂ'X@ €Cæ²±dP¬$èIÁsñå}]ÌAXØ*øaŠ{– j"³È‘é!„~% ªÄß$–2|1%–R2eA/T’!nQ)4•Ï5|±¦%æ>ƒKʹ=•Í(LZãs€F‘xêîáý€6©Ì¾Ä¥ @¯=¼v”&³Y],í* Ï(‡.Õ÷¦-€ ð¦)•"›#ƒPr¹h`P ÄÍdBâ „€(ž%@Etä.œ“…é°6ê¸ 6΂‚U£Ópð9E.kjehâw âZDÐQ>ãÊÙQ¯nªìÕÙÁjÑåÐðØ´*æç“º"æ+T\æ S`KY]ªÐÅ”c–Êr• C'JŘ$䫞"òEšJ£ÍQ¹ä Gg©Œ&–OW£^Ó6”’BL¯ŒkÐ —Æèdª>ç“¡ºj ušˆ9êz­@ót60‰¿DÓ—ñgAè_ŸŽ/VýI×u݇óézZ©i±ëaõtZGà™4t©»\¶ÿùv9imOpÕbÃaU69j«qpw:ÀÇÞ°Ê«¡s4v—}ó g^.‰“3Žgóúå²uгA> IK¿?ÏêýPFiŽœ÷´RÝj.žŸXžø*3YMÆŽ £NPµES. ZΨ㠷!Ë,WR®QiWÕv¨N»îÅa5iXÌzè?œ /ÍõØã]¯z×ëtüýñ¼<ù¼Þ¯—£j·¦á©#çm¥(ƒ¶=éû³!ÔÃpÒ÷pŠ ou=2c«ï/G!ho¨VwýÕ¬àD,,yÐ9“XKì˜ÊbÑy‚ Ða¢·­á.Ó~€» tžÀ$´®ÐSO&8á©Õª†FèµÐ¨3ŽÉ›!-pŽeqž'ÕëF5P‘™]GÆÑsÉl)Ë}O«×ìFÝv-Q$Ó¯8ðI#´$–!ºúsG×–Z «†O×óLrà5²X´3è›;6ëf¯íõ:>šE=4mSó:×íµ¸ &¾^ Œ0€¼0[u\ä92ɵ {«n‚pƒ]ÕY"y؈ñ ƒí´œzšâM­B¿šô¸íÌ[öÍAçq=¸_÷7Àóx<º[ žO§Ïg3ÏËÅŽßÍ;ˆ«Îý1€gà9Ÿ¡TÏçÍãIýínõvôr·þúvùåí’}‚—žÈêr¾±öDÌs¹›¤DTƒÈ_ã¼ù–íÃÅFú<›£ü(á…˜’N¿ãÐ7àù ±E#egјÔ91m(ˆ¸ýü[ʧ½è;ðDóg<Šœm7„…‹¿D."·g ËÉKdžúNÿ6Tö—³ßçñ”„Û^È × 1Ý­ï/ž®Ÿo×ÏDó¬žoñæßnW¿>Ÿ]tOFµ¯wG׋îíA÷ëå€çí|ú|2üz·$Àóéâíbr1 Žm}Ð5&c¿ÝÒ&£`>®N»Î¸e\¯ºÿ$Kj//fõUϾ'+Ãw–]ëñlôáz6&^ÄÄý¦I¶½S§/°*†œwtª­©ú¼¥-ÒÕ¼ePãÚžØijã¡·>ìvÛ–qN-BÛªi2Ÿµµ2hC¿i…ïêPÄXK¡ð]JÃã}½t8 ÏÇ_ÐIÎ;èúZ7G½:îΧÑÑ ª%fºP5JH¬´XйdÙ\;B˜š§s±J!樕J>Ž8OÅk6=+YyrÓ•46±¤B¿®j\20K@iO_ -ß(„V¥[U›ždˆy@ˆ!ä¥J*šÀ›×Å¢HƒO@;i‰Éòå”Ää8*Cç’•Ý@_Bz:—é\)c‹)•£ ‰,âJ!³ÄY9iPGÌKžÁA9ÕùjØtðh•Ð` .«3éªÎxµBÁ”J"‘*À›¼PÎÿ–R–É'ÙBz˜|ªUƒ£ò]+e¾L)#ÐT1(¦ãB¥XΣæET@+"S*x"Ú’ÿ ®ÆŠt¶ˆêåã•<±°…¤Â@Sº@» oˆÐ•Näã±r6!Ñ•¥T¶1à'³ÙR°ß4‹i¼²£¢> €£A*e ׉d5à R'o8ÍÉ€àI@©D¤ãð²œ@N K¶‹éÞm,ÚÓ‡1•W&þá‘ßZQÉÕhÞXÆéhè¬2THÖ ž-Ç€è@$Ë£1i–ÚW“"E¦í‰IùØ ØÌ…ªÒ¦ZÏ´jÌc(4¡W ©Û´W‹:ävÝ’yâ{ŒÌø žÁYäï·<(¨ƒ”(*Íä+Mi]‹#“¶f=›¡¦‘õ™+—+â®);áÍùz:l‰L)¹t|1£0×çó6úöd¥ç¢"ÐÍÐ6dÆ5¥É 1ê…ËE*6€Çwe¦œai€S3põÐ5Ør.t5¡’C˜R"ô$ÈÐVU9×À9x‡@2áË=.°øD›¯Ë Þ oÀâ|ƒéÖµq×múþMKÌMýšªséP+C5ÖÔ‹ƒ6´æŽ/4v9ô¯V½N  ƒ/Á¬¸`‡]«Ë¡×ñùÓym5t6öœ³yítB÷9%ÌÓ¾;îßõ¡àLZf¯*×mæì å*Åš>¤ˆ݈!]H|-áoUÐ&ñ×ðÔy:ŽQ×±Õ¡šyº²G³æñ¼ÑrùiË<Ÿ7.­ÕÀ¿:ì>Oßn–W«ÁÍÑðÒðtrvÐYkÓŽ}´¤ÈÑôZM·U·tâE£tZn»éúLö]­˜Íºch,Ú`&ð?ކOǓǓÉóéìõrùr¾x>?Î_.—.枇“Ñã2ŒÁº—Ëhçü óûËů¿âøùö×O×_>^}ýtý)–z{>€çjC_Þ®¾¼’ð™„oäó#ü Ÿ¿Æ?~ø }§£Þ|Ëõ-ñ›…‡Ü÷OLú–ÿìãÆXô¼Á›óïœóí¸ Ÿÿ´ý°&ýxÔ}ü ä|ü~¢SÎ^#ªùqÉÿÀž§»£ç{2†…ÚnP ÕÞÔ? xžÉÔ­ÕÃõ!€áù†yíܽ>¿¢`ÏÕÁÇ»#Ïÿãë,”$Ù‘5ýþïp “)˜™1™±˜¡áœ™¹÷îì¯Èêêî3w×Ì-L©ŒPBþ¹Ë%múÞ_wë«I àùv6þï»õŸ×³§mïûÍô¿Ÿ6ÿ~Ý}9lúÖÃ®× …<‘F;ޤ”LxÅS½ë‹‹žõõföý†8yf±r¾]¥‹\GÛòõn6é©ÏgðFŠy¢(BÅ5;Äã‘¡”¾M»{èÛŒ*Uã@ \¶›ªHø'‹uE¬» à!Ÿm¨†:'–Ú!îq_"i¥>d]ÏUHd …3ÎÈjÈ£X'*[ýÃW÷ÛÞõ Í&™+mcê°´™B•®þ!S' üÏè|Õ«2},QG®Ö4¥:lUXúÐÚ°òb´0¡Ò‡1Ò9ùdž¤0¥Ò§?p:k–\e꤫aÒUz‘`É —$6`þ lë'@'ÖOÿhV>Éu ;õCO£ô©zü¡U7KÀ¡ƒfùsùðúéGº~ ~àÛe¶y*3 ß¡‹›•#æõòÁéÁà´É°@ÑV‡ ƒÑ&TÖImÁS;W‹ ÖÂGMWt¡%R%t*Lܺ(5HgÖP§U‚a{Ú.QµÓN½Ü(ŸÔHÖÇӃķóñfù§;ù„ÄaõÛ` áZ§¸Nªv¤r-®UFÉ ôAç; r–ú ×,uPrå˜k”˜z‰®’éw>ýÑ89 k'8w‡kƒe’…î”D´ Ô ]ÿȵ6Týc³Œ“þ‚>ç€aŠ`€=‡2[•˜J1ÿ¤Q"ãÑÄΉDj\•kJô‰Ð9²•×>Ð¥*×ù̶?r"ó§}†§Ác‘rmÝIýäSZ=-üQ>ú@·N•Å"†UKmáp¢FéÞl«ü™ ²+}Àe ZJdþ½¦}Òi*BS—;¶Î0$®ÞÏ~›ÀfØõ\“/ö¡¶q-ÁÖ9ˆ,4‹þ/xƒM•QŶ&wBW‰|¶jŸkå&éáªÀ855Šéœà¯Í¢‡byºj뼡°‘gè2sðá)ôÉ0œOMnótåêl†}ðWõäSéèC½ôYâZ"Ó8=ü°ŸÈ®tü¡8¯ÖJŸº!óª^BÝ»¿Ü¤!ÐUhÆ®ÔOôõ49_wñj¸Î1àGáªHkb/… „>i)\M#³ú´ð”ÀB2Kz¢Á®øR,¹©2%«Èø¾Äú0T¶£€©|°…*”HÏ›É|!uy¼M”÷…O^%Ó§P±ÍÍûöjèBi7öû¾°ê™»±7Ë´Üa&‰r+,QÁ<ë3ŒäÔf€:‘I£¸X¤LíƒÆ•c—lîE{‚ë´–Ô‘"P¥Ø»‘æYŽÃŒ¬‰ xZ•"}2LŒÄá¹1ŠÕÌfÐú‘ŽÝ¬æ¼Ìr»çËPšëþ÷ûÍÃùh–[ÀžIjϳÍ$î…*i^uœÙãÜð€s Sˆ‚D7Ö&}o˜ÛýÔädšÇFà‰4ÀpèJx|ÉuÏH"­—ÛÞ jMB5‹õnb.&ñd@ºYû© ›ûR75]‹·MζQlÌÂ^¾êÚ"Èìªe4äèÔ[ 9ŠL[–è: ( 4UníXX%‰L×&~P‡kɲØA™`!ÛàÔy[™à𛇾â»JplAàê8£ëH® ƒ|lSĉD¡­Z ´#ã§HI€‘VdF•Cã,âóáHеJ‘1‘qoê¶šŠ€g¾õ]E# -sxõíÈï–ý›ir;Kîùò{_ÈÓfð´>¬ûO›€Bú¶Šôå<½Yf7«îÃnx9Kæ¹õ°.{îù4ýëél<¾^¼ÁÉ •Ô±‚1ß^.¾¾ÑÎ/òF8ïØó{úñÝ ôþoá±yO<þæ)*\@où¯?;Ί~±MÑcµO¼Í×"ú×€"ä|/^ ÙƒÊôÏž¸ß\:?Ïþ·C~ϯ¥=lžnIÏËþ„³·cHWÝÚY?Þ.ÁEwW¤cëîbz{>y¸šã@ÐÎã r~¹]x/¦çã«E~>Ž^F³ìf–¾¬ÉÈôïWÓçÝÀó¯ÇÕ=,oñnà¼^{‘”„B¿kD¡ù¢oÑÓ®=NõIªÝnºÞ.^ÎG}‡]æ:€çlä-ºæ÷ûÅù"I‹Ô ¥žEr¢Á/;&%qSkù6 æI#B’Á•°8’"DY%‹ù–yºD&$1™ý”³#hR“nøÚ¼o’‘›Hdø:Š`BX”ËQðt=»^w‡‰*uØÚWªnGÞ?žÖ…+[ìú‚œxZk©Nkl…«–;%ƒ¯;JÛQÛøüA¢5¡Ú©}ª¡A«ÄŽì“ÖÆ¶vô¡Sù`u(ÓVùƒD…8’§C×è'b°†XÆÐÂhÛ[ÕOµ“ÅœG"S¡›G€ºy ë¸U= v}§Ü©±dêS2L¦=KæñkW?wj‡l :ZÚ¼Ö,}âÚ%ˆöéC«zÌSu¦U˜šÈÔ z ©ÉÌt L×—Åö±B—|•†ð(„ªxÀN¤œV u»Â9!1Ò…+©qò™ªžÔ?“ˆâ£GŸ>`[9þT/6ÊG¥ÃÍÊa…\Ø‘Â6Éì7Õ\³!¶]­€F¹vµzü±¢>J™Ã5 YrXäÔNhئ^ÚµvåhF¶Y†ŽÅ•€y$¦Ú,}€6„:i@‡Ú…ˆˆˆÄŠ[2ÔnÍ7;¶F:UU¡à!ÓéÈDMÝR€ŸR›ŒG>dZ%ºY®RjéðS»^jÕŽO>x6™õ°SÿT,C@‚¬pרž~Fm©R{¿Hh§yÄ3•ÈS<‹œˆ‹}u»H ¥+þí`7™'xƒ· ª±ÐÛøjhüŒ<Ì8Éc äC5OˆÈ&†¹kr•Ó(Zl³È“í¥h'pdìvzø‘§ë¶&8ºtôé#^ådû!*Þ°çÁ¨Ç‰–Ó<öôÚéçãÏ?’}p˱¯ €h<“Î1C&ND5«7*‡xòaéðc˜\»,sõiߟÃÅ(œ‚qÏ‘ ר¯öBÁ<À?dÔ9 ‘­RxÅ ŸÚSii<¨f+-¼,WiE&3ŒÕ^ MSãûÝz)_™g¦#Õm¥9ÊÍaŽ:Uâ¥Ð(|ð3¤â$úZkÕwF‘|½Hn×Ùªoy°-ܼÒÙ4ÌVcN2‡5øJî ƒH¾Ùt%ê8´Ï Eæ´ðM‘ž8 +¡Öæ¡)·IèKß›âlÁ¹ºÖn‘E®(sUT¼ùz1þr9ùçYìþß/»ëYx>öÿù´&JàÐi,û‡o$A[‘h“ܧÚÅ"ù×Óö¯»å"Ó§1·…ÖöÔËÕäñrœbê£ÉíŒn–h*™cœ†€šð¹-&QÁV¥`+Á™SL@l f,/ÃÚ©)û6>m™ÌC«vÈë¾ ‰£°U¾}b«m4 _6—âo}ôÖõ:ÿŸ/ç¯W“ÿþrv·ëùj ’Æ•b‹ÑÙŠ§vXÇ­cKlÊT¹SùØ Sj(°4…šY8gZå [ç;%¾u Âq”N‘€¾>)F+ÄÏIn/†ÚÛféã0U³];þhÊM(P]lu€.\Ú“—š 5DaÚ§­úa1Î¥Ô¬ˆÄÛ_……⮟~„-ººâô8tñréjýHÛåƒL³,ÐužªI\ƒnÂ=Q9b2+LÀ£qUS¨·¡ ¹ªÁÕ[§jTíˆiž’ùô ‰ãè¤S=f 2–P5ü<)‚¥P~ùècùäS³zÜi”¨F©]?iUÁi¥Vå°S;âÛ•ÒçdXVýH¢+À§¯ÒÍ .’O«*RuH‹ÌösÐ:ýÜ<þÚ¡+GLå˜*©l‹k–ÁEB§¦r-‰®3“Vås»B¦‚–Q U¨ò¸ñCž:éc…#‘ÉФ2S†2•_Ї¼1…xð²Æ¹¿€Ö,…)Ö]­­ñC¬éÄ/TS¸R±uK+\ç@bñFj{Ê´›•\‡L‰Œr€ < eMWOÚµ2€‡jT•ãÒñG4ÝP1$W§Q1È$ÏÄWvÚ®óÅáÁ¶‚íÓfÌ„oë,0Î Ò‹diŒg H®¤ˆM™áMçHŒld•õ-즇®âPL¦&uN?aSepUL»„¢T©Õ&³¯çÝù$YÍs]n×JŸ€:ŽªŽ:ìF®)sTC¨ír„üêé'Ò®‚µ¹ïÛ².32ßjVL•ÃQåãOŽ.`7Yh4*Ÿ‘ÄE·k‡¸ª$Ðo”>3…§±Qú„*jÉ,Pg1 ¦}§)‘à Pÿñ¦ðU‚ZA†ØÄ;ó !P%Wg`ª¨<ñ9ø„Ð΢ïþóålÞ³3‡½ž?ޙ$Zdб+x&#Ч¦ÚF›à ïFz´[ƒÔ¦FêðŽÔX ÜI¬O£UßFÒvä‚yæ]s–é7+ØFA$M2]eNBƒÊ=þûê)“®•’BÖA£öWìAÂ)¦ÂûžôÝÝ2Ë ¯H/ÑÐÔL>Y³ž¯ãÞ‡‰áª„Öpöa O»uo–ÛQ°ì9 šifýãé,Ð)±u´ú·Û”àËõ›Æ3&]w”ZãŒÏ &c²ú‰1ê’¸ân¬»6ŒAÛƒ;í:ƒ®“ …tJjJü<:™'Ðæ_Âi NžYøxRžš£OæÉ±¸¢ç‹ôv‘Ø#ÛÙ˜ô]‚4ÒØHc3KíÐ×-C(œ!¼®²Ž…Ü(Òm‹7MÀå8¢ï+à]g±•¥ñ“è¼öPI OàëGº¨º™D1'!m¼cж)r’в Qâۀǔ‰r (Ux{zÉb‹cª8*KìmÆwóÞÓft¿PùÍ¥SøsöÀ³ý)Å>{þy¼Yî\¹¿"“ö±Z7K2õâíòõj~»ê=Ÿoæùý²÷çåì ?wƒ/—ão×E—Ö×ó›Yt6òþëe×eKmø¹e©mÏDûÃbÀ³yßï–ßoç@q(a{·Î<×ËôëÝ¢ɰžÈÊwÕõÞïZ®Åt2ñBä ¾ÉŽ»Î(·A;øÆÉ MزÄUЪ`ÏAnÁ~Ì"œàá› ÆF)$úbWÛÁëÝÚ7¡Û?ÑÕO£Xù~¿¼œÇ³h;òf]Óä+ŽÜ°åzæòTåSý[œLV>ñÍ#Ž!ԉǠ{‘æét»ú©rø ê¯zü mQ`pŒ¶®óM¾uÊ7Oùȧ”ºr?ÒÑLIti”™«‘o“`€2(Ef‰ödˆ§ÄSeš˜çuºuÚiœ´ëÇZùS ñªv%GfÃ#SØ2M½Ix©Òª¸ ºqâê0Üñ©‰­@W鯑Ê7`>[JÛVÚ)L?•½tÊŸºÌ7éÚ!U=ª~*}þDF²—>—>Qµc¶YÂô~€R€`T·«ÇµãOB»*¢ÌV¥]:€²d*GTé qô‰k–˜Æ1Ý8æÚ%‘ªâÛÕÃÚ yà…<·™-©B…i~bÛŸt± \Åw]Ì\Ç#ªá.:Õ¶yÔw4N€:W¥ñBSW LF!Ñãe]Çs>JéRC$޲L¡.7 ÑØ6nð€ORæ ‘RÓÚéçzé˜nÖÚõj«VðTN>ÁTŒ|š…àU>1 <: NÉÕ¤vå¤qŠçs*Òµzù´ž1Uš§+"WÇÿ"[³ .4ÇäAtû™(ÐTYCa<ØÚ:ïYRà(‘«ö'ò´òñA£r qÍÍb0îGYlöR{9ËBOŽ<¥Q=À¹°Ãd sŸíT$®åY O7"Wï§¾©ò®)ZQòfÑGÅã¨j誕“Ê_N{OwçHàÀž“ƒƒò ™Ø'ö•bH/oklìªxGÝØî'ŽÂ5}À˜¯öccïú¸¿œ^EæáÕd7R ð”²@I}eÉ9°VÔóeï|ÕÛ.²ùÐÇÇnJ(g¦¯S‰ÍE&=Nt˳Ø`˹ÃYbø«§Ó8 KOŠÍ¨pthR ˆ£ÓøZShaZôœq¬žM³I0ÏÍÐImúlî'@6ùò,7WC·˜3G‹möËí"÷¥ÐdP>ª©å«ðf]ƒ†ÙEÚ™ÊÑ)4MýÔ@ë‡Æ -Õ¤ïƒ@bG$Ó€,r4†ŽÜœdÆ,3.çÉëÕôj‘ýãi÷r9½Y÷'™y>Ϧ¹ÍÔL¡ÛÜÍvðï?ïF‰2Ï}e˜Xïï 9žø2ÎRÛ j°Éðj¹›˜óqº’g£Q5P‘À*£¾ŸDZ¿çú^Lú"•^×é÷âóñì0EÈôr{/¡/e±š'úf™÷r ̇Z/wKplÉ6EÇ’]G)˜‡C"ILÛ\WÚ{x‚@3 >‰ÁTšëÈQÑ÷±4ž«æ™×ïy6^w &±á:"0ÉÔ9Ï‘MP®f¨œ¦°‰«!îÛ}OÌh ÙÓ2ð/½]cñ/þôƒ~Ï]’Ú†)BL‹%†¨‘þ)ÅótC™Ì€¾ˆSÈ'£õö4œ ù„Í âØÁ·€È56 TìQî§žz6ÍoÝëqü´ê?,º@g@Îzð´¡.Vëiö¸üŒ^Ïç[âØy:Ÿ<ž×¿çË÷HŒÂyßûóéüûÓùËÝzßÍôr¿ý¨xÀówy'œ÷ô»·§ "? ç·ôwEúmÿ=ç¼ÓξX¤I<ÌÝ~uá\À›ïؾ\컺öðó¥ œ—û}@uC?~%¿ûkúÇýx~0Ø;ÿ¼ƒÙÙù;ùü²ÿÏõÝoŸî6{º¿Z<\Ù3~Þ]Ξñ×Õüþb ãâñbz¿ýu¿½+Þà?°Ûª÷zÚ™=Ÿ ÿq·ü÷÷«§mo7tÿçËùˆLŒÓJHh ‹o¤“j>pW#o3öŸ/ÇÏ磧³ÁÀãoWÙý¦ { ôí~EæW×Úy¤$‘bTÁ”âPéwmäÌÆ4#±'õS # ™ÀæÐÚ¸&ë˜ g9Kú¹O/Ñ=“ES€F²èÆjHhof)øùîbº¥|ûíÉëíâá|ˆËFC÷x>ÜŽýinpõ…>ñuÒo%¶…ÖøGcˆP%c?-º«QБÍ&›b)¨x™®6O'Tå€,_ux®RÙ¦DUƒM¡áûkÆ6¿™Äº@B†™À„«®+Mlå±éÛ"GU¨lðS¢•øV»yÒ¬Áf¯ì{g$^ª—t± ]%4=Õ®D¶*¾ž‡Aõ(\½Ø‰‡%·$ªdI-±sjK©S¦ª‡…;¥Ô,@›ñ ÄVY•kÒõc¤Q2ò3"ÕÐxŠïÔØV™i•[Õ#lª&Ðu™ÄçT$ºk0%×€üJvȵkµÒa­|Ô®—¨f?›å#`þå[U…mjL‹©+š‚jœÒdÙ €P§"t@ %¶y ÀÃòmü<õ oÖV¡")U(“ECĪÊWꄪ“ùR¨Òž +GÒD×$ºŠrpG¸M‘*Ët949€ŸÆ×TŽòA;v1ÃX±–÷‘ÌUº„wÄS˜V` †ÜÈ[¨ƒ¨F ªŸjTeŽ©—Oª¥#€G³v’D†VÌÇBèˆmHt“mÕJ¸†U+S5p×1Ë w%®Az¯äŽk ¦}ª+T§yLø‡3³v¹¸r³z(0uؘ P‡cihŠlØãÛ2¬DÉ" ,4%l§”„ZWgSÏXª2È=Uì »Ý*[Ïvj¶.ÌÇ]¶SeÚUdâð‹Æ‰2 ÉRž»Iˆ6ênÛ õvhP둚ô0V—CïábÜ GmëbÃ!+q4A;–Úö€m4±:®·kÈ\N"Xg¡+.Æøm=M®7P“£4ç={;ò7Cœó/Xñ×ó‹Y ÎÅ:Îò×Ó\eÊ M|Ȱ=¿ÜoÀ9ŽJy:&„à^Š!çΰ <ÒF@—Ôì‘)— Oj“a8È _ê½Ìéw]’èºYb¦‰ Æ ñ6¾’§r@>n1¨ z?KÍnî„À¡Üöì#%¡2›D›UuiÐu»™³1¶ ÁsTË’\G Ó±ßWA;Ž#†¡*€œð$ µÌó= ø†cËŽ#§±‰Ëpm1-® ‰b¸–Hú¤TÖÔ„8´Bß04ÞPCå Ò&zžŠÒÒÄ CÃÐ9Ã>)ŠL¹®‚E¡º¹—mÅ–ŠÏD¦MSTÖ2eI¢-SÊR€ŸCBtVÅ¿:ˆKóqٖ๸Z€œ k$âÚB}ö” k¤R>®Vg=ƒ›t½ëÕàvÑ»™$·Ó´ó¸|9›¼îƯ»ÉaâÕyÙMA;÷ËÁÝjày>Ÿ’™yV½a¨OÓ»í¨(«ÞŸÏç¾\–x¾Ø“À×gpÅå—G±C£Àﯗ_°çï=Yï8TÄíüâœ!^£ß|>ø¹g˜_häìçá{ŸÌÛþH· É|G&€ÖþX’ùåñ|/¿Éoò·÷ÇÅîöe¾•ó£À_3Ÿ?Iæç¿¿^ù/´Cn|ïíùR _áÛÙ™Äáót»~-¶Eüφôp!q»|½[?]Íîw#Ï÷Û€çy7úz>ù:=lû_¯§ÿçåìÿ¼ìþ¼žnÎ?×°\ú‰:êZÃÜ\ŒÂ™ôXœöí4Ò¼\Mÿº_ýu¿œgú<Õÿ¼[,±g(?ž÷íI7RÓxtµR¿kÁ¦öœù$‚ñâ[šš,TaÅ$žD&Ô²ÉyýÌœ¼Å$šÂÙÐídhš4*õ±+Ò§*W5Äô;îèöbº[v—£0vµî~{X_,’a¬<“ÁÝõÐE+g UOmé|%²èÌãú¡È7#³3é¡I2chh®Á "E W‹–95°D§rl¨j\®Î7…v‰o•"KŠm ™6™d^Á±ŽÚ½©‘¹Ë€h‡zéP—Ú¾%Z*Ó¨Š\ö5t Ωvš§ø)Ðd©Ç4P®!Pe‰tМÛS†Ê· Æéa§z˜ HC¤Ú‘H×TÚö´U!£ºŠ`‰ºD| ±˜a×iŠm\¼DÕ¶Å4HRã”ì ›t[apx§z¨° JëðÿCÔO4¡š"“ÿtªtãtßá%1M¶ @* Tįlš•Fù¸U=í4*:é„mU¥Cìl£¨Nå¨S^VTª.·«Ø²µcŽŒ‚)áo¨Ú¡£±xV¸K¡Ñæxk‘5×èH̉ÂcÏŠ)· V©‘ÃÛ€„"ꉌ;#£iNÉ´%ø$t ”I¿$‰[æß8µu UHæHhq»vX,#^bÛÇЛ|§„™#9Lë„§«t³Äuêl§^=9„ÔËÇU¼dÒ@ ª&6¸Î)×)«l³Þ8=’™Ž!± F…kA BnÖëEo5ïö3”Ò®q¡YèkŸºˆ| ¤Ñª‘~4°ÈäcàihÓ.ClC€NE&È?ñW«~t¹› üÅ$Û,È!¾¸N…£k"ÛjVk¥# 2¡“&Ž•ù TEJ“èAt·ŸyÈA=ËqtåávÓMìNã8 Aføíx&'2®SÂ;²UJÁÚˆHe<ÐW›ájíÙjb»‡ÃÌ$ó n@š~làáãk-†áz’ìçóð„Ågîê4€'qÅ?ŸÎ¾=lf][ç*g“ø~7üŸ/—7«îzà½\ÏaãÌÆaäItû˜¥J:™•ºnª405##•X¼\Wi{j{š™@޽KgÕ·§©6I4äœM#K¨‚sÈ„?‰²ZôÝëu÷ËÝrÖwe¶¶q4Ú[°Â°ù6 Â1ä†ÂWÉš§g*mG§A&ÓA8꺮ÆÜ_Î@t*[^¼í$žõ} × cÿf݇½?ë:ƒX»XâD«Õ8:Ç¶ÒÆñd†™5†‘UË$žŒÏ¿[ ¼"ÁɉÙO <@4kãžûRøv¢ù8™ô2Ø€nééÐSâPëfö^â"Ó690¨cÐ÷ÁB–É9PèŽè{²ïIQ¨Obt3k:ŽÐpç©9ì{‘¯B<‡D{®šÄN»ž«*z]/IL®ì¤[&(Bóøžøºe‰šÊ†…˜‡  ï!³‚»À¦Æ‚@BOŠoBO· À(HÕUÇB€186Ž,'[Æ)ŠçXHר(4¢È´mÉu x@Y†!ÉtMP$:(ŠumÅ6 by®ä;RàÉnqûø‰„m‘|p ®vl“A]²abë4êjÑ8µ.æùýª3M‹¸åìi=øz>ûr6}=ä !ÏÛÉËÙô™0Ïøq;y>›=í&¯—³³I2ËíÛíèl–õ"íf7þþLhçûËåßç-ñ7à¹øÏÂyóxì±døÿÕsþ-?~þt¼¼»ƒö;üèûYì¯Ìó+üÆ9ÿ‘¹Ï)níâל÷r^`ÕOàùåç{Î}Þî÷w/Ö®ðS½9žî6Ï…èÍÛs»úxo–Ï·«÷9yž çÓ׫Åå4}Ø €¯O›þæ÷|6úóvñ×Ýòålp=‹nÉ=ï&9`@÷ìÄSO m.v[kç¡4J´ËEòt1þëau·Îû.÷z9¾œÅ“Tð¼Þ.Æ™ Déçf¢2ûI5‡}’ÅšcÂRcC[H|Ù*Bt\ƒ‰=2kVB¦ Õˆo§kƒ‹›Cóˆ¶í¤!5¡ò2ŸD÷¡õx*¦z¸šÃNÔøêínÛílÏ{Ö ”DZZXvÈ »¾È“iB³YíÄ¡†‰ÜDOkÚJÝV›™'†&Û®bË a)ß$­ U²º´Á^®ú£ÔVÙzl‹ãÜÍ2»ˆÂT`…gžäi¬Ån¨âX¨]‰­uxª"2h0;°Ç¡M¨VA8Löx»qÂRU‰kIlð ‘®œ¾S™4)ñxtªl«LÕO*íêQ«rŠp4¾™~‚£ŠpåŠÊÕ®F&vSÚÐ,¡Å[rGfjJÜ‚ûåÚ€Cè6ß©´ÊdþØ~l%žM- %RUâüiW!ÅØ«0Ie›*!¥R£DFof Î9nWOë§G@ºQ­œàÊÛÕ šåêÉQåø¨z¼?#3²MaYjW¹Æ)S9’ÚžpñÏ`+1U4©ÌÖL¹ÃµK*ß(Fô€pŽÞ–ØS&ó*¥àÕ‹dW ×>Å-kB£ˆÞ©Ùh`ù Ѝ?W²å“h[P¨«Ñ@A2­[ÑÄz©¨B€  hGb«(–® Ô)Ý8¢Gx°L ÓóÐMC‹nUZõ“Nã÷këj#Yú¼]ÂÒõ2Ó¬6KÇõ“#!ÝÀYš¦¾%àºÊn=<ߎÇýP[ž%®L€ªu ížÇr"Oœè2ãb½r´+•Q/÷ã,²/wóÙ(sMIæÛÈt-i·Ã^FùÆ ™Sp†Ê†ûY Ë,h‡ ¹â@V2€ªŸ¹Wg¤ N;(§]?Q”P ;¦²˜¦}½É$ÿ @"‚FÀÇ¥QÓA9"l72PgÃ(ôØ‘PÁ†©½EW«Ánžßœ ¯¶}pÎVLÛ¶{{>ŽÞ”š*ÈSj;¡â·P±p=[õ©å“U·ŒajZ—ÃàßÝ?\LF‰~½ìÞm‡O“E׎tjÑsÁ×gãIßßO[¤Š­ýÌÕDÝÌõ]i\’ïÈ޲å±û–bê Œ¬ªZp$t]eZ–)ÏÓP¬®sŽ#›&±mâòI0\LûSZÈS/ LÇ’‘\ÝwTl/ƒgx nÓ/Öð “î?g4ð`wO†þ¨ï.'Ѥç&‹6|ÎYõîWƒÛy¹[tŸ6Ã/Èå¼àœÉÓäL·ãÇIcûåj¹êgÓìér1íº£Ü~ºY~¾í|¹úú|YPXâòùIØò].¿½ÏlüÒÛuñ‹üêêùm‡¿¥Ýç—Þ©æWLúq:äãz~È—ß/ãõwÁ]à^ Œ!Rìó~k—¿œ÷ò½ü×ŸŽ w—ÎÙó=çWæ9ÛÿÜ»­~§¬½óêâG—Ö®pììÞEàœl×ò“0oöqËžçëÅóår9ÍžÏ&/»Ñ+äomöõzöåbü¸éÞÌãûUþ?¯gË¡;ÊôùÈïÆ*€'²yhŽÞénaØ5gd‚/WÓÜf/çñݦ7& ”}½_m&訛S˜~=dz¹À`¬­æi?³ú¹5(x¦·&5`F‹,“5£É|;¡<µqàBCe¡’øZQ¨¿Qf-F®$²¹ç›Õz–^ïÆ. ¼9éòz¬F¾Æ–Ðþ,z6°Ç‘볓ÿÄQ£LÄâÙ<¦RâÒƒXv´†«·cG€ÊwNu¡}Z;9ñE‡|´ùe·¸eèP‘:©’­tÚ•“úÉ Û< L:qy™9‘ÙÓIÏÖŦ&4#WÖĆÜé¥6GU 2ß,U¶SÖ$4§h·+l»¤ðÐMB;mhÌ¡ª*v"ýJ ×*¶m(S$ø6ìJ¤k@M Á h“Ñ ¡G#Þ’Àu±#R5¦Q–Ù–.P")¡Ñ‹­å8žô<²3KÑ3ÉœÃÁê±£²SˆÐªí*°G¦ Ó2qm\[mÕØv  2mMdéVÕÒ¶ScZ;smMèà\L³„+Ôø¶!Pß;U¡UÖ¹–Êu·q© ;h˜ÕxV¸æ‚Oðè M¹¹_4ÖT`_÷×>aÛ'*O8g2Äê±'¡Îr›Giäø­ñ•Ðfc‡3Äš«µ}‚LCn—1Þ¹b®<2Œˆ!CIèOÊ'Óí‚‚ ¹­ËxÔ ]¢E¦Éuj$–©~J‘8Iæ¿‚bª\§"]"Ý î;ñŽ¡8²¥±m0Òé©Ä7n/—šL1í2C:T™-¿ð€h àçLã|;»½\Ÿm¦€]a‘ƒ‡Ý£ë®%?ÜìÖóÊô,eÐ ¦¥I¬¡p‘mqÙ§—zýÌ#u1˜½Q9É âñtÌü&R¶N\LC§5àz¢®ÔËìé0JC½—˜)Tƒ'¯¦)È$v¥å(^Mb|eóAÐ uG¡@þËa¸F»Yvw>>[f›Y¼‰+ SãbÝ¥¬Æ1ôH5Ô³EwÚóÖ“%ä¡6Ìl;W›(};Mþ|:ƒÅá(­if=œ¿Þ,/çY¨SÃXŸ?çr;ÌBM“Zx5® Hpt•Ç’Û0¨‹7N ™>õÔ¾ú~ Â»ß Ð€‚¦™9ŒÕinâ,Ï×ói×Ê=ñz;¸ÞI dk¡#:,•rt­ê ™ôÆPI ÁÓ,†vV|Gf?1]B›³Æ©ÃO}š‹®ƒÖo5ð»žÔ W_u§ –¾×‹T]¨ç²™¦‘#à€ýnÏg›Y†ç<E³AÔ¢d<¢qÏö}ȨëöÉñ¨çucˆ²œf³QœÇ&Þ, g{`@õl” ‘Ff–v¤É(bâf |5ÏPÐKjC€:ËYv±ç 1IªëM&Y¿õúÑlÞŸLRè¹r› Ÿ¦AÆ‹Y° @Žç)®«´Üµ,°–`è¤#L/F“9–ÎI"•¸*, Nj‘G–’ï¥V¿ëä©‘@$:$'þ¸›e6úëi P¥æjÜïFw›ÁažþÓfP„(9¯çÓ¯—ó/—‹ç3p΄ó°ß­‡wëÑøç|Žô²çßmG·»1hv9о<î¾TC0à !ÀW{*øúrAäx^.ß~!ÍoòŸ;|y~‡³½S‚ô+Ø~YøX~§âDož–´ðó­ê¥p7=ÿJ;W…ü`žòwày¹üázž¯ûž/÷;¿Ôþð‡Þç ^ßçüõ«A£§ªÙ®üé-ýRÑKáóÙóìüût»"±ÖdÑÕÝhr1{¾˜_LÓûÍàµ`žÇíàq7øv3ÿïçÝ?ï–/h(2$³ ôå8\O£Í$™õ]²&Kf,FþjlÆÁ×[”6úÇ#¸C_äæÓÙp5p7£ðËí†O\Œ‚œ =ïp‘/Í'áíC¤‚®G}'‹5ÏfaQ&¡\D2«y¬áÈ‘žô]ÔÉý¶k=TÔÔ\ŽaHª°Üǹ;úç—khp(ýú)™‹æfÛ¿;Þ훑?Iu4}ÞÉ\`7”W#o1°€:«‘ 2H¤ÕØ‹lÊÑ&sÀ²°^[åS€D›‘Ì¿¡&oÊU•;éS°Md³ [ÊIå*žÞA™86´è,F¹~¾ÌbG$¡ L­Q>èŠÂ7ÚµSX£2×àI‡H¨£Š –‹ÁC3Æž ÚÁÅ7˧8*€‰äk d~:µSKabWU…¶Ì5m#q5TÕV˜b>XÃC7N÷Ì`«´)S°yAJ†DˆÈ€&h(e0ß© ØaæÎW=®s ´(– ®ápÏà±?×,A¦!Su¾Uáš……ÞáZ•½û‚kWùNmáFÚ¶&èãèÛ®p*¨ ºžkƒªÅüɧ\áá‘é: 4…Ž#Ž'3±€$Á6…½\#=b“'Ó m2A¬`žÂ+(ëRÝÖÚ YH‹ÙûÜ_Î# °ÊºIþ-2ç­gÒ>YiZÈ øê¨-Gky&ÚŒ­‚2•&@§OA,¤Û45§ÃÏÕèÀâPH!Y\„|K¡| ¬ÛÕwG¸ôT#ÆfHÀ'[£ñ¾$¶e[™¨™A ¸ ²>…H–{”‚²@Eù¡Å¥®8íÚ°°l©à!ëGôYn¢í@‹%l4@Ñå2Æš¯SýXƒ|·æ›Bì)0|‹÷L´ôNï»ölÁ—…/Ñ5x‹Ì¡ä®§°†îw=Žf]{7oÖ½«E~6M I}?2Y\ÿÕº?ˆõÌ“º‚Ÿçþ¥®ŒÃ•Î Ø‚oò€@ž9vØãS…m2î™ôüù8™(r§ƒÀÓÏœ4ÐÒÀd.dÔõ<“~ÔMlïȨiãaÌu€73DbÐÒïú£A¸œå‹iv¹›Ì'Iì«ý®7èù“Q2%¾¯Û¶â¤#)Ï °Dü<Qhd© ÔIpO#ÝOžf#¤¢ÐŠ|3,Û@5Yb;‰FÆy}Oîæn–Ú¾«@ÎÌEϦ¾\Lw£‡õàqKžïçÛyÂö|úz1#ÀsŽöÀ3¹ßŒî7ãÇÝôåb¾ùOÒÇóÙz÷"ÿðÛÓÅ·Âéñíµž'"{7Èo¾—ËßÝ8ÿ‹üÍ]óí£.OAÿ+ð|û 'E?Úëïû¿y–ÞQç!ôòCˆ?°ñ&À¶½“çõ‘ÈÞ½ƒÌ9çûÌâ–/ "zÏ?ÿ5ÿל¢„ýS:ÿOùÏ®´}úls¿-<¤« ‰5 /—xÈJ³¯7«›eïvÙ{\@³ÛÁÓÙè¯ûÕ¿¿\þûõ̳êZÛþÅ"äæ´ç€yиÁÚšð%zË1¾D †ÒŸëo·‹>n°ó$Vo×Ýói4Ë­»ÝðËÝj> ¡‰ƒ®5ê9y¢umØCų€7–Þ \>$ÏaBÈ_ÏÓݺ‹=‡¹Ň‘í1\©9F¥MŒAj 9UXÀ}¹é¹ßüõzµ]äÅœo%z›iôt5ý÷Ÿ7]OôÕvlÒ¢ÕTè’)Ö€(Ó®;ô~uÑí,X ]¨ÈÌ—']È·KdÔO»³+ ßb6ÓÀѱËt#q˜¡i"pÎnžA;¦\·äú0UÏ—éÕ¶7ë{S6WùF CgªŒ¶m&Û.±­×)C“º¦€fêRÛЛí€I}-´%À Zuž i/éR» ™N`‹Y ut õÁKl]è” ±­¶šDÞJmØ¡B§"Ñ5ªNºWT®Ã4` ’ÉT ¼)ãÙZ*X ×PQ…ùÅ(Fá¸q‰nwP§Æ·«E0sÍ’̨LBw¸v<ƒKeZ¸°Žg )F•!ËY -œ´U)A:5r§Å”‰%23Û4EÊ’h\ƒ^ôšJ›¬/ ìáª2[1•5`c¿N gß³ ±ÜnŸh:t.¨B-ÕêØDBT! Æä…+i|œX ´“¹J1+` ˆåY,!((X‚Í|%.š>²Â£/¸zGå”ÄeäÐþ"$tHg«¥¶C—æFâKà®bML)ã= -À­*O]è)Ã^ É”ÎbšÃô&ûؚ*uî¯×7—ËA×=4¼ÊíÕÊ·mòt#öÍ®Nù=éZ°t2O˜æÆÓÅè|Úù Ÿeß':l·ËUžy"Üåª;ë90ÇîΧ± f¬fÀüx}x§y¤‡Žè5•ÎÛ‹žª‹GG­l´lí=Øx¸©åÐÏ=i3Žî6ýëU÷~;|<ŸÜïF“ÔôTj”˜ÃÈ„zÏW¶“ ïÝn|½^¡Õ½Z‚j2W™äîbˆ‡l£ýAΖ8|ÌYB"À3ÇvLzIÜù0šôϸç7=„Ó‹í~êLz![ȸŒŠ®!ꘫŒú$3ìyj“Î&OÍRgÐ G ¦qÚÏÝÍ¢·[ ú9¨€ø†`ªa'vœ8ad~ý 4ÒxœÌg]Ð8'ðQvÙû˜Â'.öq³Ø‹CÛ¨ûæ°g±m“8a9â9än[  àŠïª¦!ÚdP˜f™²C"Ç4¥À7=OO'Œ0$âyªaðH™¢"`›¸°È„„j{¨Ûyl§¡‘ú sÆ}°_ˆ£&õÉ0üQß#³/’™É™AFF᥂®Gáõ²÷°½œOŸvã'‚:ØŽžÏ'/3h¨°yÂgÓ§ó䉋ùëÕ Ø³êù7ëáýÙt”Ú€Õ‡«åWB#W?çy‡µ5_®—×ÛAâ‹Y¨ôSGõ‹ù»ˆ¯&3lƒ =!‹ÁêR–¨Y¢ zÖxàr²[ì‰Pv?˜Ç›ö½ò8²J €FRO0ÄZÑ·bÆ®9dê•«IEwT vßd®îÁfôU™©¦.é« Qa"yÚ·»¡0ÉšÔX(#¥d"¸AfôS-öø4#ÇJdX41ÿiCîà5¤·…Ù¢ÕMBÃÔXPÈÖe¾™Ff/s‹å"†§]UÄ6Zìnê2ªÀ4-M਺&1ºÂe1ÔY Îæ ö´ëÕa7êgì«VÇ8ß,“!Ò\§y{yº0o °¥ G‚ˆ|šÃG¾¬ÉmElžvs¹2T§Û,ybµ›UCm÷»fè øîF$²NÏÉŠKæ(wHOÖ8~¸Z\m‡ËQ8ʬÝ<›÷}X=…›Â8_t‹.­üþbr½œ-²ÐbÏ–ùׇ-ößÎIà @”ù0D9ß -z>Ô·or»eÌЋõ4%ù]בÅõ# ð°ìûóìvÅ%ì'p»Ûr_Y ËÀ)'NBƒÅÓÆ¥&…¯/rø~¢ÝF©¾z÷»ÁÕ2{¼¿\Í.—ùrà],²ïXaÃD_œmÅåª÷xµ± TÍÖ8·;Äí{2ž4à ß&^®­²2×t4~Òf£Ä3ùõ,_áQ„n|œY“Ìz½ž_.òçËÙ_»½^â.4¶z¹è^-{®ÜÉi5·ãd=Œú6J¬E?€œÏ{g³îjßÍ.Â6ŸÄF?&¾5Xa…ÃÇëÅæb”Lû!¶³a ¤w<”A†V(šöãÅ(]Œ²ùÿBÒÉ îgžï(ih0ú@£Üëf^ *ˆA;Ѱ‡ÉzÑ"ÏÅn¼œf #I¿ç÷p ƒh¹NgÝÙ¬»XôòÜó¤©3ƽnÐíúàœ8²ýd2èÇI±ðv“ÈC 7ÎhƒIÒØô]2z+ð•> /1‹ô,ÔÓPÍÉ"­€v:@ËÀËå ¸^žÁ0g“‡ Y-ØSxrFÈä¼^ο^/_¯¨§rÎçÏ  ÎãÅüùrùõv³'³üñ|~µxÖ“ôõaßßDhçÛëuá0!ÚüÛóÕ÷—«ï$§ðö¼øÙë÷w¿ÍÞ×±—÷Ìù{OË[¿OÑ!õ£ © }9¿áüK³¡Ÿpõ£—Ï—ÿUžßä0óC¾¼ùaþyýéóù 0{°yûë ®Þðïåê£~¡ç_:ÔŠ›}ÙGøüàœ¢{ŽÜx1Œ$Ht±%DÈg³Œöi²ÆÄâf;úr»þÇãù¿¿Ýýëa÷çíêëÍâûÝê¯{Ïú_›ÿyÚ=m×óôlš@¿ƒsÐváóœö\`skƒÌ\ŽƒÅÀ»Ýö¿ß¯^.'_®g»bº­×«Ùõª7NMX=dhêÈ裡o2ôÅ|ä/&á”ôpÙ4¶€œ^nŒG^žé ™“YŽ<)ö%Ògá‰}Ò}fí£øR_Þ+ hgØŒ—ó×ÛõãÕüùvŠiVg w‹Óùê85vÓxÊ •@§$ª ,»Äá±¶&¹/§®`I ¦Q•éª.¨]â©UU®…ðb3Âq}6ê%ª)×#wÒ³B›‰nÚwÏù¸¸Èù²ÛpU2ˆ!ֻЖ*Tª¢Øn¨çæh4Ý Ñ5«i†v2ŽÒæG¹Ú8LɪëÛ³Qêò¡Aßnú¤wiù {åÈq¤m÷þ¯eg¦»åÊzï½/ï%•JR÷˜Ýó&(©{f¾ýO‚Áb ‚È' À:扭I¨f‰¾­=Gé¡×¢M¬&×Pý.§9ðŒ*àëë¨B^Aép)8ìŠ6<éq´)ÃF³Ü_T!¾â,ÔP'½«ežŒì- gU8U€±ç‰ùûyýzšé\„óõ4?@’΢m鑉`™–ž¼Î¡Êòðüó°*"ƒ/ýa]îgqâH¨«çÓÕëéSæÞc$¾ªKCg…W& +­<²šÜƒ¶{Z™8eââ˜EVà(IhE¾º*ZcšYuT0mÊž«Ù¦ä»êrž¢aÔ…Wçn•9äê4/SÛd¹ìÛ¤©ã¹ mÙR[`ž(4ey¤©Ó00tsÕuÓ~àq,O15Á6E´p´FZrY{tulêSd&ô5dÆÐx® 8òü@‘'ÃÁ­® –)M§]Eûžf™BJô5Û–\p›P&r|DŽÁÓª˜4¤…ÏÚ4ÍšG¨ƒM{™’̓vl’‹VRŠ“ž ¶DýoKÀCîdzøq‘Òù*;Íc6˜•y^öÍëav¿H>€ç~‰tþ´UI8ü qM¥ÿBr|ùü´y~Ú¶ÀÓIž ¶¯O»Öa¦Å!&Ù×oæ ï^4tl¯<µónK¹¼1 xiÛÂO›ó{YµõüËË;;µw1:Z½=·% ö¬Ëÿ3<=¶á xXÊC°ù0dýPðïÀƒÛ7ø÷|Þ²l¿±ÐÓ{´w³Ï[©ß<œÉEyÉì]+òüaƒbçû%Ê‚¢‘_#Ÿ‡ãŒ˜“&Î/^î¯Ëmâ-ïªàŒ7ÛDgBY2ÑœWé×Cõçãâ¿OëSí¯£ dè\y —‘ÛbhrÌH86ÕoNROh"] ÁÒ©>6~¤ ÁÆÐ­rO\æÖËý  ‚!ry<õEßžV¹™DrÊ86•ƒ“Рi&"H‰/¡ÑFäÎ4´9ZãÔ—€+èÙf™y¿É Ð}}XÜo‹Àä,ZNg(Cã_I£/Ðãb›‹íiDÛ ‹ÜN\qÚû¹]˜åîÚìtäéãu6©SÅ6`´T¸Á¿´2à ²*sWÂø38çöÓ¿ÄñG4k ½²ˆôÀâ û#‡Óþ/ w­vL ÞÒ´ŽÛµë­…`Ò¼#Œnz7ÿâ†×@Zìåê_ŸÿEÀÃîÏèf'}rÅÄù¸÷©û/(#¦:™3‰Ì“GBn*7dá{ü|u€:äÏÐ'G(2žÉC²n†6:\{_ƽ«þÍ/“Þõ¸ûEštÀTÇÂøJïÀ(›òÙ } ÉkE £Þõ/ÜànØù‚ Mð¸[S)SZ]–Yu$ò#µi‘ÛÌ—ÙùŸ<$¿ Îû`žÄS}S„˜˜ôˆv Ò‡OÈùQ“ŸR½ 2ÜnQØKíyèVšÿð¯èãIïçQ÷- ‡´åÝ¿c@»ÿh¤‰4˜E còWIÀGÞ”þÌ–iúÄ&¯ýDÃ|€ÀÑ 8Gžö<Ò´Kã£;ÐΤøÌ’FWâð ÚóȺå(Î8¦0O¡£º@;‘;ðD>¯Š·–Ú×$(•=äÓÑGdÿq84lü&´1ôzžÇ¾1¯b¶Î§«_~÷¯AA¦ÆAáüúÓO –Aç‹¥K×_~¹¡tCÍOúª4%€é^úù§Ð³Ïìw®n®~Öhš&OUqlëbàêŽ)Êqnjü§Ÿ~º»úI“'º<¤“ø¾0¥UdÙ2û´–ìdx=^‰sóå'cpi¤o—i¨Uf{4Qˆƒ4ß4qê©Ûy¼[¤`è KácF“ÇÉv– 摚H¯]ÓqN¾3çbÿçmé8r/ÐÇ»@`¸_Äë:@Ó¥m‚†tåhc~e'U¾§ÒD?§ž²]$’óÂEžËÈ@0Äôš:1—…Z\ë«ÒLnׄU¤]fø+wþý¼ýõañz¬ÿ8¯V™¹«Üßæ›ÂyÞ–•/ÏBýÐD«|™9ëÂ?‚@½m=š:±=ƒ )råÀ•Ùò;@_£H<±“@¯2䃋¾£è*™MUVE˜ÆNž-[`À€…vBpâyºeº”¹—ÆÖißT¹³[åEB#\‘¯‘ý'²bôŠÊÔ±eבM'ÏOá¾–¥”rè¦ÁHÀ!9 È‘y$ hÉ¢eª<&æ#9&€üsãʉEá5—¹L²-‘£íÎûŠ4"¯cqäZ’kJ–ÆëòJŠæ¢€ŽŒãÈ ùe&—'Ú‡ æÁÓMQ—FPTÛÑ=4à26f…3/M¤i2¡'¡m·Ø%:rD`jêJÀÑmÜWÙ¡ e€“ã,94ñý"{ÚTO›xÓ†cùX^6õý<{Z—_ œl ‹çÙq™gžZ%Ö~•³a¦Õã²y¹lA>ÄŒ.dáÙ}{>¼^öl hÅ A;fñ B T ZصhÄÌA×?Ì&k&ý)&®´ÌÀ,BÛ7û …öäÇ!ª3ÚŸ$ì);§}ʶM¼v~C”·DÞ > `ZKΛgÃòC¾=¸åñ>¡¼×½çíåãç#±‹/ísÛ”[pB dÌê¬?FÖÚ¡±÷ð½š-Þ=‚VßÃiõ‚ü—ãâ n«Ã,Ƈö@îŠçmñ°ˆÏËäÛ¡þãañçyõßç-~Îc|zš-ö=m²m¢È‚›d¸X:EbÐXP¯Ìéý2þõ~öõP_¶yÉf†žŽe(=í‹ã"j'¦.ÔÿAĆÃB—¯r# ñq e¦7……‹±/4…{d1ŽU M“K\aQÚU¬æT'jd#'Þ·‡Ùi=nÒ¯§Æ×Çèý?¸2¤Ó³¤"T{J£„„·»¼)wÑTî ‹:GI[ÎéÝü4¸ûÉ» ]ê˜jçxG¾=ñ¬ sæ!3 *¬^#¶—ÇõOÌ1¸Ø1å1žN53¸ANl•›®¤ñͤû³BÞË8¿Ò„;“v0é#Ñà~ôd8ü,qWxœ­é´8¼­sì˜#2€ÎùÇ<’Çý›<ñ!“ás¼áêð¢Ü^ý‚d¢cHUÌ›ÌÔÄ/Ÿ~¦CÛTT™ëÞ]ɾ2?œ÷»77_~õïtUÔa2ìÈüP˜t#ß ”ê|5S…DCš"7ºƒÈÀOž¦ý eªHCIèÓ:·òXœv! dú‰ éB@Ô¹G{~q]à ù‰i“YîÎso·ÈšÌI<å´)«Ü”‡9ÔG–§w­mèûe^Fæª×Uˆþ!v$ð„zâˆ@pÈ"·îWIke ÌRã~SœÖ ÔÕ'Û2óñ…¢aþ²ª¼Ã"™§Vî+e¨‚Ip¯¯­ŠûYr¬£‡Eö°Ì÷‹üqÅhg]ž7Õy]nË𼮞ÖÕ¾Špüõ´z\‡Y2OœeÜo*|³Ü;çÙ <Û—çÑÎ?/ o^ßIãÂŒ!ô“‰þ—À"0àÙµÃCïaKá uÞŽŒ"Þ“%hù>õA2ÿ fÙþxckùHäã¯ö×ù x¶o†–=º~"ð` îÚ¼1tyc3äó‘XF;À³iY)ŸV?>÷é͈´þ±hï×[ÑòÝŨ¥& Ïx° ?÷Ín†ÀÜâuŸfÑe“¿ìðgž×ÿyÚ ü÷e÷º¯×ø`cÓSÇ®:nR»ô&sg•Ÿ§&°9p„Àœ¢OÛÕÅßU¯ûj_û‘>Ú–öeŸçŸ¹üeWîjë1³ €""`c%DÖâ1 •4@* 9\«uf”1˜YoRrpY 'rO8.$þ¸I^åë©nb]}ñÔ¡)t}}Âõ~¡YrÚû]ºSpz-K¢[þz^/kwY;ôtt§&ixûùnx`È#h‹Ê§©adk-­7¸›yŒj`‹¦2„&ŸU ׺Ôm·äöЬR J™h³‘âí"ÍC—†ý»_~:ª&‘,N²Èô-A—z¾Å<€4Ž>A@âídXö”[aLVœp£kðŒ£O™í"´û«ÊüÌIá»c6„$ÓÌôëIïfÔ½šnq>ì|ž®É÷’3 Ò@Gß‚˜ IK;Ý+@δ Ú±^šôØÀÖ@Úí'ˆþÛvJˆ©Œ-2[u§ý/‘ȳ<~™ô>Óxº8Àƒxüð–Þâ8éQú8ÚÊDß!µiï“M>KGÊ“+]ì¤Ì-ÊÕ5i2ƒV!à®mu(ó·†Lž``KizÒnÁA>&sô¿Üð3j/õ†š™2?gTˆARŒÊ‹Î¼'M»­ÝI‡¾‰îüzÒ¥ýž†´]ò/òäÆ3&eb€å€¸l¹¹+qzEÎEüÍdø‹Œ'j¶Æm›‚Æì2o|—Ì£ uÉ`¥‰ÃÞµ,Œ¦ÃÎõçO¶!•YpÜ.¶«ûdÕu…§ƒé¸;Ü…¾µhòÀ3n¤éÜ|êÜ~FÀ¹çè†&:¦âÙš,N_äúÀUK¸¥Ó‚Mèé¶.„®æ;jäéý»OPÙü¯®®L@;²0€âÌÎo¡5C‹GÉÃÓ‘Uq@^ôÓŽ0!ï¦IÿK™óŸ—~ƒ^ªîi± nÇð9*3ñU:»--Ê`Qx¤&ušÌ^UAâHÔ]$VìˆÇU ªôi•Æ6[Üi™¢'YVA›äÛ³.¹+¯FwŸªH9Íו¿Î‹€d‰] Î~/h{qó·§õׇÅy'AµÁ÷òa€¤DS&~ÎsSú‹”†ãÑó®!}t¨ |°ÂøýÍ©LÌí" ‘ y‘¾®‚Øç™˜<ù!oËã"EnOK¨üY›¾6FÙ¿ÞÏ O*}éq"óH}\%§Y¸¯¼o§Ù#-Œ–=¬òyb‘m§ ›È uÙbäHä‘’XYlΪØCž6­½Ãædiyl%!”Gƒ°|=b@B¾:ÀS24Þw”Y–‰:J™¦)ÙŽjY²ïiqDC]EêxæU€ôÉp™Û’˜ùEä$ SÒ”Ik>B“(dÓ”1@‹¹ Ñ’Ed6v& ÞK\ý¾ ؆¬žæ»ªªL\ˆeø®†¬BG®C´5ÞÑx Ž£ó X”±HìÀUˆÇÛ¡–:‡ÄÓÐŒ\4H,bkY‡¼¯:5]sBÀc£ ×hXx}ËÒß΢Ç-yà\¶õ› g™#\6åëañ¼›=ïg8>m ÅSðÏ¡Iž@>«ò8Kqÿîëø4Ï–™7KÝÝ<ÅGÞþr¿<xZ«€ç²%Îi™‡¼Iù¿ÍeÿEþŠ=/ç¿£HûW{Þ¢N¾^ö_Y"ß‘àÓÎá-Ú_çòqÎ"\~ø ¹e`ó<­Ñ¦E/ää}Të=µ·a,öï{þðü^þŠ1ë¿gã¯ðöqýéÃ#ú¯ÀsA8­žÖ¯›Çm}˜§@ÓÞÎìš=4¿Ý/(œæßŽ ÂË]åW¾RGzdòž†¦"‡¶Y_fv™[UnG­zî+‹Ä|X&_õó¶¸l‹:Tæ±ú´ËÖú(õåPã‹FßåªCK4Ú§&”m&,²Ÿ­B^=.ï[º •*3qóD.Ÿx´µhŠ˜¾èéÃ*VO«ø²/ÎÛì´ˆÖé·ÇEhLó£žéæÈTrˆ'ar£€ôhu\ˆò;P:nÈèÿr÷åÚ¿ù<ê\õo?¡¤…f¾OÆý/±§‚ñp¿¾Ð6‘4åª;¼û‚øÀžÞõ§ÎÕ§Á…ÈQ²ÐttÌA6Cñ'ýÏ,÷„8”É}è ùÞ}Ribļu‰²ø!U®q ΄€ÈC=D¶§×­ë2sFú¬ðצL{P–±f‘+­K)€s¸]¦Šjw$±Ô¨²Zµõ:4Dcæ¯/¨1µ4"qQ@d¸óIwµè…r‘SÓàFÝ!«Pê¡Â[¨:Zñކö„`oD“zH'íSÄéõxø ´#ñ·ªL”ëÛSך %ƒ=Ô†`£+¼*Mfe»ãþíí—Ï"Èïê³&s@~Ü› îCÎѾ§ õÒ½¾¹ú<uϰ ÈIW¡ª‹Ž¥*âDƆ&~âÐqLɳ6ýœìÿt" ûϽ»ÏÃÞU•«Y~wý¨#qБÈÓtÏRSL#K“'ß÷,ÉÒ8È#0€ÇÇhŸôhZ × )°D×àÐÈ™q²ëêø0u¨9>¶>Xq€ïªôa]×ùº—•Xå/÷ä™ìé“Ç] =ºŽM¨Òܹ¯âxZå ‚í,^”>9·‹ýYæÔ‰Z¼«ëØ Aœˆ´o@¶ ™Ujdñ  u奮ìÙÏ#ü¼‰m¡Š4Cè 0ì‘A>êô=Òa—¾hãMá>m °‡‹ÄØVÞ¾ñK_F_´, ÌÛYøú¸L|ßZH“å;øºÛO_œ*ô‰–™-4¦Q­x\É]]ì©\'°xߘZÒ€fë—ÊxX¤(#€ç¼­6Åi™ó½Ï¡9½êué&‡<<®ÒUf®Ú9Ît¿Ïû겫V¹[Bb ÀžU̽È"K¬S+FFSxmçàK¬â†ˆo™‡|ZLáZ²ç¨Ž%%‘ù†©NñºgEÐ>N€žgø¾Ì a)_Ëb ÚYÍ“¦ ŠÜó¸¶d["mªr8"A NpdX¥ròÌ© GÆ-¡¯æ©ÐRå Yœ A‡¶ÁÇlÊÈ!@7“s§ž¦NeilxBÀ£òh̺<ñQCô‘[ ¼z±³Ð`·«á˜FF‘Ò žcðä§DþÛr䩈Öäî¬ðð¦šÜ¶”:(¡ž1í€É:ÇUv¿.7åeßœ7åý"ð\6ÀæeG$ƒðrX¼æD;»æ¼®öMü¸*–Îéßý¨³Ê¼‡uyZ³Ì­ øÝóaþp˜‘ôÚ<Ñ0Ö_€çÒŽj}p ¡Îþjȱgû0Ïy bسû 6<þ-”ÔW6Lö†óÿ‡:—^ùÇíßé‚ä¤Å’7 ÏG~¿gïoV¦7δ¼[~˜U篑ÿ™Â{fþrÞRÓwã…XXŸ[ ’%ìY]Nˇmõ´k^Ž‹3 Pæçaí·ãìë±ùüzšƒ|þ|ÞâKœ'42îª#[CÑ@çY‚­Ê!/Ç’PŸ¹¡-™ÓçmùÛýüóêa§öt–¨À’Ua¡×B/¸k‚Ìc ŽG£CN@¹Ù€'òDˆŒ"§‰ýù½Ö ®€¬l­(2,M:­Q÷ ¸õÙFƾ!ÈDc꛴€.vØ.ÛSCê°ùbƒ<’ éÓ6"l*írI¤ (/ñ­U¾Óå>Mæ@€¤ƒC*±%éz¨Ÿvü@I‡ÏwŸiEDœº¦ýëÎÕg6¸ÖE~t]¢äjœ@^‹Pêùžv®Ž™kÓ-?¾Ò¤¾ì‘û†:ÔAV“+‘¿µÍ·£pD{ö¬)à§õO&}‘u®¯ëÛ«+À7êâŠ.sšÄqãžoë¡kà|Ø¿!4’§£Ám÷î „£H ®Èâ¸ß½º»¹â§}nÒ›Žº8éw®Dn0ÜN†·€“–=™êòÔwT[§£»vB NZ?èÝ]9– æv¯Ý+’*CŒAÓž*$/®U¸ øÁ‹¦ùΦˆ+´À 䦧ÍË JÝБ!Pð–ÑÐf6ótVøçÃl»H J Vƒy S£kœ¬ªð~SB›öôi“ÚÛ&åV±">µdø×”†di"K@UAREúyWIŠ@-´Am væ63K-(éøð5îλøyZeèRG:-³YldŽøœØUÀ „Ë®|ÚRŸÖ$ú¯OëEá6©u¿-‹Ø@'U =>Ô>O°ý4ðÉã:€§Îì:wØbŒ´šÐ‚dA¥Bé…W'æq•ƒyvód¨ËÝo+qp…âàá~•½¢=ÍVɾöCmT¸â23Ÿ¶Å_÷¯§yN³Ôûû&Ú”þ,±ö³¸eÅYî7ó¤Î sÉEbA ç4]‹Ìn€Ÿ˜f£k½8=‹ìÐÕñ¢]SÎbgVF‰¯ƒy¡Éý<²ÒØ9ƒ«£À <=/¥NžÚQ µGp (ˆˆÅÀ®ñ¢¡ טÀÔùýaùŸçퟗÍ¿¾ÝÏ—¹S† Ô|\³ÒKBMWG)ÛØ¥HÌUå£çÙ7áœyÀ<¿Þ£©ÔóD/.žõý:ÛAkËí?^¶ÛB™ÞÆ\Ðjcôœ€(ž*ßõ ®J,¨íÓÁUAÙV#«2½Ê ™¿uIéø X2¯ü=R^%ÛE4+­ØçÇý«Þí•.wÉnãIª@NlìæªÕþ d-uNkr«ÉM`›&$P„>E¹Ž&C¦ôMu´[åÚ® ˆÒ}‹Ö³¥µr É9ÔAÁé0#Lݯ0¾á@ÄàH|t'O{ Bî«é𺵽ƒU¸ ]{W2‡~{ÔFHdYª‹¨½©Ì yê´70ÆR§èÏuyº²¡Œéè‹Ó.RF:Â%2Ï^æ‡Ó»w!¾kí4è›àòؙ܊ã;d 0#ŽA,·­ùÈǸ±™%Œn!>ØE²>Å´h›ÅöÑ'A³ Cîhâm»k[KA–:ЉpˆýP.É@§Ì—‰JŠ p#zJûúÄ)9êÈ\Odž„‚÷w(HgÒ»uÈ®¥‰CDòá\áè'ò9º»2¤‘Ê÷'=šÆ%²éÉÌ êOAú V~|=^á¨=ÏOr·dÿQ‡l®+ŽcÈ¡ÃO†‰Å3¯²Qÿ–õpÇ,ò×°P€Î5XÈ1°À Ê, F´.4­Ö:ÊZéÄV§h$ó†mG§Mh0†ÆžVAZÌ(DFu€4à–"ó’È,5Uà’SI~ζì˜"³çØ‘GcpÌÅÚMÙ³ª<@:¶Ió¹ ¤¯‹È’k«hçDï–âèB;uJ\–äÿ°«Wu˜új»«Y2+ƒ–  Ö2›Ü#à‰m0áªAÓõ‰À3Mïb‘{ÛY|¿*λúþy[?nªËž 8àœçý¬µö°™æs²íìf š‡U‰pZ丂ðz\Þ/‹Mç" M„¶ÜdÞa]Ý#fˆ8?®A8—7àÙãØBH ? “WÞiH°ý0õ¼£Î[xãŠóÛ]tKkí9ïÚDÞ}rÞqèü7æùHêHv m~`¤à¹|§íJ=ÑÚîå=cd€:ïÎ4Dõ¿LIÀógÚ‹ïèòW¾ú+\}”83˜ùó¼aOëüsÿa8z÷qº_¿ÏyÃ^wýí´¸l«oÇù+M¾kþ8¯{\þû‚«2 Ùèà ÚŒ P›ÂÍÌ*iA'mÕ„Ûy Åêõ8ÿï×ãy“ßÏã¯ÇæeF/R#6Çø¢ˆ›Ò{Ú•«Âi×<µŒ¡’ˆUj䤯¬€zJ´ ‘'E¾ŒcãS Rhy®ˆ4Ž *ç"YÏÃÀ没ôûw×Ûka æá5e¬JC‰Gjò¢ö‡7äÊž²­šÌ\ÏTB(E¬ÑÆñúÕ9É ®!²! ¦9ˆÂQ?92_t†kæTÃ9ô/ |›Ê”9éè’!sâBü–õA;Òt8î[n¸Ñh§K[x¦`kS¶ ™_ÐÃvmÚ5é2_—kæÃÜaV£ ¾KÞ¿ÊH¢ýŒ®qpÒ„!"sƒ›Á- á Ñ‘pË»™¥>˜ÙôRTPnëuc«ýØÅÉÀ5h^¿gŒ? £pxÔê®.ÑZ‹3d¡Ú[àÑÄð ôNkÿjOÀ¢Ô€8é!{À<®N2 5ƒŽQ™Ö`HGçhgÚµ¡Q2÷H6è†D€šwlŠ+^ÇÔÒÆ(5G@ugªcCEz²*÷-}¸b §v+zôhrH§ÃþÝMè™®¥M†]E˜ º7 „óý>ò,üš :Uå±ß¹¹)Ò ÌY‹´µP`£HP -BÛ»öo,¶Â rÑ6ÓYZyâê Ú°‰0ÝM†·#2MGÿdS>ê®ûH¼H}Mæ UhÊØ³U™Æža@¹Ö…EƒÑлnbèÝ›Y\ÅVh‰Ë*‰mÁÓÆø´ï7l°çå4GœËa–ù àg]ù!»k sm Üu\¤@‹5Ø Ô–ø<¡Åë2 f!˜gSûxDÍ\q÷u•Úš4ðLí¯«-{`ËÐ hÉb|ÅQufÛD4à*éM‘±'‡eÖêS@­ÁRz[»Y¼Ÿ§ûy¢M;±--‹àq[ÍQºÔ~>4>ï`ÿyÙþçu÷ß×ݺ° _þ翞~=¯‘aÐÎãºhbÀƒ°,h•éÌW<ûEÚz@!xP·›&.-žÚJì¢_Õñf³È®ó[$î²NÀ9 ‚"²ªÔðäià9Zàꀓ"%Çæ*÷³ÄÎS'õ$2ÓÄŠ##ŽigŠY!Nè逢"u#4 uZ¾m ˆ\æ^,-f1n÷]Õ§Ä5ßQŠÄ©sÀFsBˉ}ðhÊã:š¢p¶¥ KÀrgB ’šÔ[U »,|´¼zT2^Yì£ ÷«°]ÄP3=ç…_Æê·T‰™¸2ˆ´ŠÍ*4–¹·kâÓ"Câ\öä¢ì¡áª- äC?ç<1«PGàÍiQàÊËqù|X<ífÛ:^—á=Xh]žVÅ¢Pó¨Þ‡Ãü„”"œxÀ!Ï{0Ï;ðÐùs 6íù‡ç²ÿ»ÙçéFž?X臌‘ØIûÄó÷Ÿ—²Í›çÇÔ¾'õð¼Åüí©­õ{B Ýä 9ÈÐ`þ3¸…°„ï¶®¤PÀñD¤ ‘ä[ó‰¢QÅÜ-z¦PÄñtÔíu€C·–.®ð/7êê2§â}Mzh6Ûe‰¶N o9j Ê¼yJZ#^–ˆª“ùÞv™3pn$ß¡“?lêEá:j> ôyÄž ö-!J<µNhX›Ø ‡žÞ¤¤Rè³Ü•&wÖÛz¿Ì Ë4¾ÃVîéa™>lKCèæ¾‚+®6ÆOï ·ùö¸¤žŒ+§u¾È]@Ž0¼,÷5:Ä÷³ø8OêH¯C ×ó*|õíaQGÚ~më`ÛDe¤CŒž¶å ý¹%Ó / Gæzl–G–@a€+(ŽcàhÙž®ì°ÇòH_S:P´³®ÃM!þß–œ¿ϾÍÉ[Ê-Æü?§ý÷/€Ô²ÍÓ6¢Çï?òÿ«3]Xyÿnhú¡\­9èoáoÑÚÇý<ïáíÞ§vFØýú¸.ÏûÙëÃ꼫_™yçq™ÿv¿úõ´üüûióÛãêÏçí~Æ6W'FÈ36=O ß³H/ò„ð 9AÛúö€ø»ÿ¼ìO‹äiCãÑÏûr[¹­‹ PgSze ® ûi•¡3¥^⊀œYnÓćԌié• ä…gÓ|Ã"µWrŒ) =ˆÝÞf=›Ã*;®Ò‡]y96¿>­¡ú­kˆý!úºY-F¯S‹†nÑyŠ}€›c>Ãîm¿{ËO‡ütн½NÜ 4ä<“xÒ²‚kía`]r%Ô¹…§ >ÖÕhᔎ@þÉ=flé3» „È=Í;Vè{5\Mú·ÌE§Ç“, FÝ[… ã>.JSòú÷n§ÃÛé€"[Úâ |ª!¼Ñ¸ÐQ‘fkAšlZè¸õh%cˆÐK }¾*ôÈ™!³ÙþÎ\˸mÝ„p/P7‚Iu 5™J„w!Ñ:Òw=mrx“‡râ Ž>ˆ\ðUÇfvžˆ­ëˆö`©£Ö7[¦• »W|Ÿ­ðOC{È<$¯L.(ˆÐcîw ZÀE¨.YB6×!ªÀi(7º5i"¿ÀœÉi´B¤Ùúx4'QU÷°Tóûó® uæöì]lCû³M¡å–7 z³½ãÇÓþÍÛ§M™‰Z„xù!ï0öv½MçãìÞÍ™F—Þž‹@ Ãþú!'»lÿ-üóâGA~ ¨Ýåí"C $ry7Lµ`É¥vOgàßæ|OãnçÓêa?ðüö|øãù€¯ï¸È.»æqU<³±­_¶Ì³úý¼š³µ¼šÔl2|Arëuá$!Z¬µ]fëY´[fhQ.Í»ÌÿûõÔ..zh§mñÛãÝÕã:ÛÕ~æJÛ*@·S…>öß/Û‡Mš\∳̦ôStn“Û´…D TP+<ú”kqÚŠ8ú²5N·«ö‹t–9@ô‡/§ùå8CG‡Þ/5G,šÇVבi¶Å °ÄvÁ=]2ŸÛQ9i¨áJèJ4Ã1ИLŠ?z¼¦ð3‡M±lBÈ/]êB@8´] rÐ-)MŠT²PsMì‡ÜB8Ú´xr-ïÖ›Œú’0‘Å DžECñn] =—ul3$ZÀgžÛë& —¡e¼h|ÏšŸ 1… ’GÜßÓDv—vpÙDV•Cç¼A˜ :t%qÎÑ$Èô±Âdȵ1„{hä3+gJ'ÂÝð`ƒ£kJwЪBGwtI—¦27ø°)iâ°u}áGnS¶t5†`sÛ;xM6Í•pă 3<ë!‡´Ü«-› ùHSAØÛ,µd°iV“eíf¡4/íÅžšG + œ¹§¦Œ—2†œòtü“¸Êvž´¨C7¹W>êÝ,ºd¾‚Žç›Ðâq‚OžÙRBšVxy ÅŽ$ØÎ"„&¶ Âö³Ø–¹'_öõqA›éܯR„ý,E¼Þ/OžÁµ;r¶¼ä«´Ã|(>Žôu°@®%úŽœ†Æ¼QRp,a3K€jEd€ÙÖM »$ !pwó’Ú&IC ÎºŽËÐØÔáq™!c°çCýíañõ~Ôùö¸üýy{XÒ‚Õ s½Ë­+S‡yA|"KB¹Ÿ§Çeþ|\@஫ˆle‘ÛòqY,‹`‘uâ.˸Jé%–‰‹—Ȇ´œ2u' ‘2AAófeŒ×=/ãØ7A&UÔqË<(s?KÝ$¶ŠÜ[.²Å<Í3w^Ç‹:Y6 yù0ü3+C¤ šÊc«Le7…@Fž"D›D²À¡*óqKÉò@#Y©‹V„&˜톱Ð\}˱T4N´Iè\±«£ ³ÌŸeÞ¶I6uè=,ª™EZ%J1/ƒý²l,ë؃6f^Ðà—ƒ—x‚nšø0Ï9/­[ó@¾°Éæë²EB Ûšµ£ZÀ¡Ó¢¸_–çMƒãe7?.òóv¶›¥Ëãª<­«‡m}¿©·³tAJtXg„pÛUÉÆS6$s!yç0Úiæ lZ¨øgøðüóúåÞhü§ô£§7¼ùÁL´ÿZ?ÄyC‹wJù°Þ´‘Ÿ[èvàìrø`’¿ØOÿÈ$…ÒÎÓî/þ¯€ø¯ïþHù¼û›uë/¡v¨‹°çû#8ퟷžÝ2ÿú¸ýãåø²e®é­›:” ?ž¶œ×Àž?_vûY8Ï̉¾¶JÍEíÏ+ï´-7ó„äÉ]ßàÅ]ùÖN$®«“›ÇX>YÏœ‹Ó.þJHyž–·âÈ6‚~™IIL;À£+ÓŽ“É  r9Òå[&1”þRiò2ºtò]—Zq9’…)?AÞy¶¾œøüO»ùËyzäósÜЬU袓\ÕaX´w† 11¥ÑPÅ#¡gi¤oz–ÈÆÆìà˜a—ˆ‚ëócBé +Lz“~„Î&}UœP¸‘*H Åž¦£Ú2ê*z*tãø‰.Ý $nÆ}$…G e@ÝŽjÙŽc—MNSÔ `€àÁBÎer"‚ðÕA>”1 ?hè•iâ@v˜ÖÕ®‘ã7mDe©ý2AÞDϬ°L•)6‡ÑÞ@¿ºˆ< ¡P㡸Q½ZojiÚ4ÃH„"éEËâbëžü(‰HQpèì|à ¯–Ю:ä¾å£¨U ™vh>a ë!¸– ]Yì*RÙs¦ª<„ÜóhòØC;DœNCcbÀ¿Q-ÔziiY´‚Aàê&rÖëú½Ð3ÔŰ' cSð¯k©¦&z=ôùE Üp<ì¡Ûç&ýå¼lªD•9EšŠüh4èi*ï¢~¹Ažúl ±i€7ŠH B‚à"m6ª xŒx"ßJµƒ>˜ÖT˜33Å' ¡á’&TrÈ5ƒ ;œgËø B'huøò üƒ–Š6h¬ˆ¥¢AÛºLÃd-m‘³4*Ÿö5 ÿ æ¼QÒ&ªjì)à„À–<ƒ$´haÙ„óí,®SÛVFE¨G¶ˆoyYzUd4 ­l ò)Ù9NNëâù´8,³yîBåÇÏûMÉÆ’nyÚ7ûYâ©£yj{Xmk]zχæaMóâÑüvÙâ)ÌýƦ¯L› O`+ÆçuP|42a4±(°%6saJò¥ñAk»Ÿð¢ôcG^ÑÔa[KZ˜9,¡þÏ3wr(H^Xi"ÐÚy\ñ7ùÓ¾Z—î¶ ï×ùëÃò逊§"Ì~€â ¡Ÿyʶ!‘GHØ&Á Pjž ÀqTdÍS¯Iœu7)ž¯›lV„Ë:)A>‘'x¡È̳@Ug^“û³<\ϲE£$UfA9ã$v’Èn—S.2¯.C¶ÊA†àAšM ©€‘¸âGZü0÷ˆvÊÏZ4)Z~Ù¸E$#rJf"­Î|:æ ¢Eö¬JО][EûÄ‚F;+â&ËÈYUɦIËÐDóZãªb¿Èv‹EX5ÉvžÕ)ÍG‹íà/(à€@´ðϦŽ[÷BëêÌŸ·³7ài-<ÌEç¼fŸmsfÆ tŽ+3 ìæ{†:žónö¸›6 £Ó’ZohÎr,JtX2صæ á©Åž7ày½¼sZ³ÏËåÃôíãüåýßK™Xbÿ.Ðßâ¼SÄþùo¡ËÆ<¿cÌËÛxÙ¡‰—dzÚ1£îú1…—wkÏ÷h—\}ÀÌ[ä§éå¼;·Àóô=Ÿ—÷»žþBïI½|eû ýËå_Ÿö‡Å~U¼>nþx==Ÿ–—Ãìy?¿ìšˆwœÿö¸þíqEÀó¼½ìË]Ì3«IÌ2¦}“•·™Gè” ”Ñ@jd¸Ú$qåß/;šwy¿Ä`žç}}^g_O³ßî«ì¼-×è¬mßD¨l…·k"ü®÷«¬ŒÍÐNëü@†ë´oJ}PÓïÏ;ô“©¯&ô’5èŽÃ2¡e+êÌàƒ×júª8À‡ Ðe"63ÅÊc› ¾\Ƈ‰.ÚRZ=œû¶ÔîLÇzÔ<Ôy…í8 „ΑI!Í9]šj"7î÷øÉx:ìg±¿‚,7HIŒVC«Cކä4¿Àj]m³Hk gQ£_ò!—#—–×Ð¥!¤¥Ìh~Èæ {&=•¹ñ¨´ ˧…Ç Pabª( ßZxp]œö˜(G)OþɶôÊðM‡5$Sá¥é¬C"ÐE‘&Ïûf–ZèëV…»ŸÇ—}ìõa1ÏœËìYf-J#š9×tÁ*6¿>¬~=oП …?/»?m^öÕë¡ùýaùí~ÑŽGïÑkáÆØ:ÎÓEj{Êè´Ìžö5ó?ThõËÊG½>öd¨äi ¥útZ|=o9sš»áBºß‡-J–yò¿Ÿ÷¿=mp£o :eK…T嘚6Z'Ãí2g³- ² žhz&M<p Y}!d€+c™à [ç[³¨À¥Õ;Õv ÚÑy „©Ž²H¯2»e!f»˜ÊäÄ2–¦#né,¨’À @ÇšDŽ£›èÝ_˜ œÐZ94‘"ÈiÚ‚:+]–méÛúù¤Á ZnÜZZ,6fÑØrsúŽ.x4qbAÐh¢NœƒR<“!eI GbÈcò½D‰PLVØ·_Zà1dk"¨Zuþ-ê´ºÒBcP@ü ¹IGŽ>Ä(‰HrÅÏö‰¨RÏ”P‡Ì©Ã.PXW,b=𢕒¹09:Íý÷-ŽÆìäA;ðDó…Õqä*d_R9607ö-9—ɇgÈF£P'(ÑÄ59[Ÿà퇮ĎxôÁ·Á½Fë䚥À?:y\ñ¹g€š4T؉–ʱ4A¢m§I`—8iÒ÷@¥† ¢v|zG íDNPÌ N6TžŸ ›’ØF•ÆèÒÙR„œ0BŸÍb&MÂUy0 ‚ïšgs˜Ø0 lÛTž£ƒ<†ÀtÉeæÐDhR‰;–þÁY0ØLøaê"€ñÙ~‘Zè/æLÄвÌq`·3ˆ*œ¯š בÃÒ³Š¡‰#šhlJdó:YÔiäëEr™OÐeHš< }³^>M·á=KáFf’KH‡*"Ú©"C‡¶X´È!t¨Eé¯ÉJCËâ‹Î|•­ÃìÑDNërÁ&C¡ó9,‰p.ÇùÓa†hv Ëi±,}W<°™8‘%äžú°)Ñ]€sÐ ¤Žt\@U'”íîÐB¶Ó«ï€eÛ°½û>½ç¼‡íryidÒêÓèĪ`M[‰%5Èg‘¡Kôtðf¥€y !¬ÑI>î›U »‡¦¶$lÐÎÃ&G®–¥w9Îr_ÕøáïÏûÿþvÎi Šé¼ð‘æºIŽ«rUE`$UGö,õ@ˆÄ­=4€Ìpe¿È—e¸¤•b¨¨ƒ—5«ã¬Yø¹œ%ËyRånšedmçÙa]U™y¦XdÁb^ÔU’%~»EæY€‹óÿ²ÿPni¬Ñ÷§o¶,½÷–Aï$µ™™ÿd¥VÏôìÞQA˜B¨–Õ²ØmˆZ¸e°F¬ŸV‡ŸÀ³z<òSb-Ìs\Ú¹œ×oH`6¾•÷Hðþ9]gõþWЛÁç›ìÐýâ_{…±WWÚê×8¢7ÈùeDµ¯f¥_ðLîè ÀþQóÿÜþ;’¨_9ÇÜÂ;‡Ýoб g¢1ï¨k `[.É"÷«àùÆm*y1ÚÔ$xÞT Ê·Ï_çå÷C}Yeû&\—ÊHêö#«ð‹®–G%:ï§åË®þ*CÚká¥} ðH*Âmõu·Ø-$‰7¥ôÇ‹h~XÄOá×eÀ±‹Ä2Ì“GÓÄUh@©Åº§)/@,º‹¥d¤B;ßO-Ýõ¼-¡zµU =eÜé½Î²wºCú1Éê9º}×ð¿ždçCeû2Þö4ƒ?[§Ÿë6¢|(NGÓn>þµ.Ä3ä¸Äj¿­st(Šh’ÀPYùåîÓ‡û/ˆÎO¬|ú^ºCêeÑÜ· nx Ó5f1'¼…ú‚4ŒûHØÿû'õzCw'A/2*äÖŒŠÕ«pò}[ýéu!ºƒf ¢°¼—ÑXÿ¯Ö§? -ÿôZ™ :¬žÍ˜ë9œÛ¤pàà ä#j’sÊ A‰ð¢ã‹p×V2aœ´¡±áÐ8âÅJü¬n²[cðáÒr¡GÚün ÌÆÊã—ÿYcžÔä3—ÜÝ_Æ Êäf"C·@Ð;wΣä9Þ<€ CûÐááFî>ý›†jáÇ™¹GnêÃ||“ÇôÀCѺ–qÿ§ Ý„ßþrf÷“\÷qNeDô÷‡·ƒ{ö|>PÛ›ÛÏÿCߟOAŽOÐ˧ÿ»ýÛH¢¿G™ñ‹ItL ˆŸKmh¼0:E$&Bß ÇàñËä)“A|*˜öïy"w\k仓v6z¼ÿ2Aʪ" <ëÓ‡?¾ðóóÇ?ÆÃG¾eÛl‡€%ÎsóéQÿŽK &sNÖ¹–±êˆ¸?ÂE`Dûôo ‡zµ†ý›‘¦â‘d¼CAr°ðÈêܯ2&AwÔÏE7ŽvßT1r“e™ú¬\n:†»u0É–Ét,C~ä¯ñ#·ÆÉA>y9u&ÓÙøa2¸Õ8Ÿñ%ßàÝÇ?$°Ç¤þÔßKr¹Ø²G·ÖðÆnïÿö¤ÎàöVÖ@‹º-Àg| ö È·¹·_¦PЉåHvšãªug´®¢3àáѧŽËÈ©$˜LoÚå®ZÜñã^?wþˆàõeþ |ºáF`R‡WCD70žï˜œ6㮎`€§Ò’}—çÑ\rãø“M“Èi{X„sÖxêÌ›¢²-2ûiW=ïëC— »Õ:ëV“Ùujõoþ–~Ä2¶Ú2Ø¢üÖ‰„Ù̇rÒÄK"×÷`ž©çNAYšBF¸ûj8J=ÕøËš2äÃñ¬±í°¤—xBw,^H»ùÖ’q¦=H,óÌ@&ÎVg¡#Àsû–2¶xTe·È^×Ix¹?xVU̽·¹Om¶Mºïж+ÙÈÍÆˆ<ˆÄJýq›» /¬$\eàU¾Α¤:¸Ó²€pÞOf€‡•ó² õ_è¨Ëü6ñ6UlPg#–´´NĉÖA\¹ê„ Ž= häíf±àY¾qÎù´¾®( üÂ?+¥š_Aèý!æÖÞ┎Ì nýWùÕÂs=-—[8lïêa]<ßϫ˦>¯«ƒ„l]Ây^Ã<•ó,Ì ­çmqê°g‘ˆU‡¯’N)óŽô3u,s(·©ýíÐü¸œú“>¿À Á†üA'©rdœÓnÞp:úœÂo™cMîu†GcrùŒ!Óø|BwÄ $“Έ­à–‘ª²UÌ,br§—îsuÄH#™å>{w\huœ½;1Y<ÜüŸÛOÜ}†I>Ó¥x’žåŽéÛ@âÎèÝpöäx©w÷¹wûYÎóåC_Ö?õï>IJŸûO4ŽºÏ>˜´? í£™1qN_aL¢£‡Ã,Á0:ØY¢¦‡÷ d¢ôdŸOÓþ“ gÜû8zü ™-íG §“|ì^äxLd83ê}¤AxXNpÎýgw6€yŒc‹æ’ÈáÞZx:üä; 5üdì<°Ítô…¥dòÝd‘pN‡lytg=M}˜ û½Û›ÛOÜû—ûÏŸn?ýŸû›° `3J$ðãÝç»/'£žŽ„ú"éûTš÷@‡ú2ÜÊk˜SÚˆN0¹#“dç£Á­f>‘i‰îo?xŽ˜üî>>Ü}ô`ä·_>Î&Ã2OX¿ùüa<ì}þáÜLǃ9§»ý|wóÉšñÏwÉÌ)“¾ùéZc‰ù´Fà‹«8Öˆ2÷€¡^z®æ(k>k4 íH”…7AŽó†$á|½,z³*)u•YÀ9‡½›Ù5ˆmº‰”ÉøÁ,½›þ£L^ æ©…¤7Ü«²3 mþÁW&ŽÎÒ¬ b¬¢&`*¤j@†^fýϱ;z9´¬?íËÂÏÃ)ëÐN¤3Û6e{ì­ÑÍj™-'˜÷–9•mæzØÔáªô]Fi%™ÆŒ3ˆ…g&3tÈT{8¿³Üøìš fÈz"žÉzÄï?Ê t |§è;‘;ÊÐû¬>À¶È<àÅŸõe´…¿HÝ<˜¹ãûEêÔ™½ëdl©y‘بKo߯»6¦nÈhx‰Û„ÿé‘òèê÷>;“ÇÌŸµyЕ@é „~ÞdaX± DÙ‰7˃yZ‘=Bú/² P@­iÄig‡ó$†dì0˜ÆÑ<&Д-<É´ÝëEÞÔ›g£(tò,Œ#ø‰CYò‚I ìšå‘ ê°„^À*ö> “äiRm ª)^§ÈŸ‰UP3Dùö˜%Ÿ*˜Äj ª²`QDž5âäi$ÑŠà·:¥‰R`†Ç³H¼U/‹Òˆ1,$¥6[»-RŸÖ!.µ§-y¾«Ôt5T£PE^ÊQQÇàç 6 Ñyõs<'vk²}•lËhÍùC›WjßdË•<ßÂE˃µÊXn6ƒfůÇ2_.²ý®Ým#g,Á†Óú(¢XK~Ðr4¶õùÕÔsÝá´¾šz®?W§×Ìi¿àÍêô«èôΔd8çüöÓXio?Wo»fxs]]-E¯ t»Ê«åç ž¬ýòÕš´:ýR%Ùò“^®Hse¡ëú/ÀÓý™Þÿuz­ü»À§ÿ óüêãûy6 êÞ­+:´eï–ÅËqyÙ5’‡K¯JžU–œ–©ólË1õT¦"¬¾n«§u¾obºš:u"G²}¢ËÐm%Û¤{ã§åŸ§åË–£šó2Ù•þy™B)ß˯ûæ¤éžö‹¸‰­pú°¥+üÌ•á ,§OTÜÙ=’®J¬&w[ÉW?¬ÇPG§~;¶›Nù’û£C×á¤MæM„Ø®¦ßÜÑ›UÁ|t; Ó‘tâ_˜Žn)/Š«Ù±Žœ…^%ö'wŸ?Ø0•?†I‡Î* +Ü‘†ë|™ÐY –,²ï>}`‹§ê0bîæã5/|>Þƒû›Ñ#Ë[€x˜«UÄž>6eP¤ödøyÔÿ8ìôP~?%YíüãÎ,>³GáéȨ±Mq¬Ìˆ7}œïÆ¢""Um)2œ„Ã0  å“8bnØ ž‘‘â•°Çb:÷¡¦; E²ìêðw>¦2\Tüæsdâð“Ü >LÃøáŽ¥‚èòáþ󇩯fÌF÷S5רKŒ9ýûÔ“o?r'T¢¦$iJöøe¨’ŽÃ *BP G®åÖúÊØ7â„ z”dGîÀ—Ñp2è 䙺þ¨þ‘‡‘4ûíDïH¦`Ö<ó´­5½IZ¶ˆeÄÒwú¼2Jºÿ™gºjN¥O³…©ä}ïööö7{ópsûpó…Güùƒ¼62qç¸÷xÿåöóÇþã?‘ûÏ@ÍY2š{ “¹Ú1LX8ÒŸn?DZ¡Þχ½™<‚£ ‰[-–Idƒ¾;ûòécïáöæóG.1› FƒÖ…¬†w·ŸÀ¡Ï?ΧCßµ¨´kOçÓÁ˜g1„1úYì!-djuklÏGlqæ#ž‘L€´²'³ Ìó`ÏÔ !(.- Má5@ywè´ƒÙa³¨sÝf‹2lÊ1DýwÊ2òç¼Eñ—W2„™%¯Í¨Ëz.®ìÌuö’Ü×°/Ìðñ‹ ˜‰Åé†wéÉK^¥¨“øÓ*qŠ˜Ýk ï°* a³ˆ×‹(ÐXLz€>;¸Ódë’ötËLÍÙƒç}³B>]£ í˶†yüÙãFV%/œ?A-Zf®Ø“—èé2t\ã§“4<öLÆ\®¸WÀ㠤®=6–+Á¹á­5ëe±½[•HvoÞç|mr;ÀFìŽóp¾iq¨uEWr¾{E²¤«øwȲɜ®ôeî -ÐÎ Ýr›±§Ï·?º§_uô«çk‚B›ÀêÊÚi²€%²>´Ç¾5ª³ GSOš1¢ƒ€ÊïµÎe‰ðxî$ªŠÌ«‹ Olâ ³ñ!l–yÌyÄY,x£œã€:iìWyœ' ™x¼K£{´?ˆeÁ™C»rb[†ƒ©§2r%ÛÍR¦Ê5} /X9Ò–QW‰õ vZ5%p&ª…À'ž_äŒSˆ(q»*†.àœ2²W*ªNëºÉ| ÷rÖ‹ÄŸ¼Y?u'™7m2o·L7šYÔç2{-âÒâAìo®œóˆŸ+ýGpò†êe~÷êÀªB^˜àØUÔgÕ `v‘7§·AÊðIr6j+CÎ9øIµÍ@›©Ff¥{6dåN•q¡;1t@Sr ºù|ôÈíÁ#½Û•d>ß~£$éÙƒ,²¦cåøb ç1(Å-` D¹££>È xul‰o‹"Ind¢U{"‰¯ §&µÒˆù •Ê– ƒî¸4Õ©•Ø¡‡Îüp @Á›éxØøÀ£œOÄÇDcëÝŠ7PùNÆŒLz´Øàávð¯Þ=ÜÜ<ÞÞQžÞÝ „3x„£{÷_ï¾ È'CIhÓ»ÿÔ™n:mH5tL“𞸊ìaYe(ÞØ‹L±{÷@æ#,a\? Èýí§œÜ»ï=Þú÷_>}b#èÒ×àdši ÚQ­›OB>÷_€Ÿéd€jm ÏŒ†ƒû~ï–ý£À¶æÃÀ³”[E“¼.Ãm#¶ Ûœ×Õ˾a‰,#Ϫط)Í«º|qZ–§NVŒ“k['VØy·H$,¹³±lRo]Å«‚*©m˨7îTÌboÆÇ¢H[[%ÇÝrÝ•»ms2\—bçÙ/Ïâ¨Ú‹ Ë'Yß(x¬ Õ¼•77Ö[lÏ{£ÍéÕ¢r0–Óú°kßX~ÚˆÌίëë˜tN‡7«Ñõß·3+À¬ŒHm#ÝéÕÚúáöyºlŸŸvG5aÉ! ]Gs6æpÓ†ŒÃë¸ú}ÌrÑÏøm½¥²õ•µœ¨Ãnù“ºƒ2$Õ>ì¯çyk+;Isu¨r¼c¼ó¼fçÍ⼩!Þ§uuY—h@b„éòm>mÊo‡æeW?.œ¤°í|•±çÍq™,•C"wˆîùãU›, J9­ÅLô´’†—6=· ˜”ØhêënP=I˜P]…³El•ጾhYÈŠFÒkH ⪠:^5´ûô‡Y0u¦“þg$N&ËÖèæ´­dvæm¹_åÓþ'tÿÿêq”;Cë†ÎLÒ‚õn¾AGœ%Á7ê}H“™‹ÁOfàK¹v÷‰C8áàþ‹¹rVLX“u}A¹FÖð€T™ÇºVÏ™=X»î;}ÖŶ3“—X½f2=¢É‘µd–äñ‡¸óG0 ¬`Å’AU7P…hqBÑÃÇQžùÄM{´•šeѻƒdn)·àÍíýí—ÞÃݰw/E ;°V}LÎ@Ù_b›oÔ€v{µž neª {4>ÞÝÈôC:>wâÌÆM™Á¸‘gÞtÎ3? ËÏŸg“ÑdÔôî¢ÀOlk–çNoo>µó!ôpÿùî–³ Ùáñá‹=;Ö$×&ýaÿ~>ÌgC‡s€Öueâ»sGñÉž!©-“®‘]¤^;‰èõ3w.™x‘V³áb ’‰U{¥ðÓ³%L—Wt½ÌÁ!ÞC^ZVÚ*–IšœQ]Š7U&Q_!Ie#³XN%û.ËLRµÈ‹Ê«âH,ëýlxÛ1ËçP¥¨ÿr- g&ÓE[øû.Û¶‰;¹G±‚s«oÜ[uêHz½ÔÑñà6ðƒ^¿,üó¶!"{@×!,´@÷ΛJô5‰’Çn0ƺŽQñ6 ª qíW%º ç÷ç2xn Ý1Ÿ•¯á+¶ØÊ¦W/Yân–%mÈéMeŽGæV©»Z$©?ºÈt_0 ÛýFëŒÏ5OŽˆò2êÊ©ªfœcW|nËèï?/« _Žm¹|t™DÛ‚ØFtȪ¤¾ €c©?Ê£IY±7„Ó[…øü±Ô$\á³¢R@¢qd:NDü£¯)¼Ø"a´‹™ Ÿ™«-Þu®™N{÷â\p'¾3†gLè)§R£Í-G)äÜ+WNBe³áãX3Îͤÿþy¼ýl¶À-ä%ÿY>ÞZ“ÁèQIéì~&ýûk¨Ï­„ ñ“ý§qR ר€X¶½ üãêÜ…}©ê­ÉóÃìO‹©•æF£n>KÜ£=¤Paï ö‘¸ÃЃ|Än£Ô÷ÙD.™t¯,¹)_S „j@ 4²§÷@/$ÌC“ÎP½„¨cƒÉàap#åŸ?›[Ö´r\¢ÏóÖÔ4w£þ}ïþfл,(OÿÃGeÈçA¨i>Œû´!XAѤ¶dxn4±ÉÀuÇPδ8ˆ›Cf#®Òî% ò|({2ö\ÇšN†7ð ï÷dôèØãÀŸ†÷qhw£Á½5ÂÓ Kx¦÷pê4‹˜hoÁEeG¡ã:ShãÎПCßâ(äJ:0O¨ž ÄVžxüL#™# ·(BÅ.ëä¼_²’Fðî5„|e”«‚¸“\a *`€‡—GÌ9éÿyådc˜y(öVðs2—áðw¼*’€Åð9ó¶lÚ|»Ì!ŸEî;“ÇÄ›hŠDï–b}KOUöøÚÓ]”AW…‘3B6¹g¢…ù«S™ˆŠ„ C¿qXUb­êˆ:°¹&®e.£‰uFÔØ´»*Ú,sý9Îßr×8hpsc‚sí±Ù–\‘¡'JÚM}1h â9RÿŽÅíPù2¶D>®ŠuÂ`žº´RoJg‹XGŽ7YP'n;kíg¼éã®IŸvÍ_OÛ&Ons?r¥¤ÎX?®~`׋찪7,»r׿\Ñ–ô/‚1‘;]7ùiÛÔ™G÷uÙ5uêÑæ…ï›kôºúÁá2iWy¡ëÚ‘Œ=»ZlG§%@µ*e6œ…dNpËpZÈ4ycÕÝ ºÞ£/ÒX¸9ú]3}€[žÃŤ{òh»ƒÃ*:4O¼ôÁä¸.éonœÉú#Zd»,A2ª**e~½TèHÃÓº:­é°§¨[ìñ‡óa¾œVœù¼-Ÿ™®b¡3¡ $ÐåZÐÂ9•Ú±¦Jü´C±&·uæ ÏV™‹ iélz–{hIøF‡Y群BõÆ2W£Õ7–™ÀEB,ЈNj,9Otöaôt haM{®æ,U]ïbæì3ƒMF2$‡Kô}›.º/~®ÙÀÖ_*îïuæš¾¡ ¶B¬ôïåC>PÍP > üÌ ÿp8ûwj*ékÈÐDÜýÖˆ^šk=܈ˉv¬èÍö¸_ öƒŒmh n“®›Öàv 5¹÷1¬r݂ܜè ðÌÄ$+ *=ur‰ï9§o÷ùƒ£H âpZÒ$"ž  ¾g6ž {ô4á°Œ@°œ±Ã­z.€›aÿ¡ÿxí<ªÁÇXx·{¦Ãžgq§Z€¶önÞæë¤Í}Iª?Ae­ÊÐu Í"Ûx¸½mÊTPDcx\G,0žcõï$.høè:‚@IìE:ÄppË ØóLǽùtàÚSÀ&}¨f:饉ï¹SÉšfÓðÿ,9 œí¨¼žW‘­9óЕÈ×M†÷Dä³jòu›³"¼YçcÐÑ ‘äË æ©ú,d¼aì˜uøGÝ?Ž5ƒŠÑ÷ù(:dV“ÂKyžV‘@Gâì@)æîEÔVqŒ,ct®®F\]ÉÄ;±F÷¡Œâ™¦\ÈŸ­É÷˰©Ç›Ñ ŒN:'ß5P±È<ŠÉÄ¢±+þYåOùxÙYÌξ$££ûâ»>o*PqÖd>Ɉçx+ãšu $‡Ã¥%Txü«³j!ÔxXâŽôf€++vXEʧj¹2ÚZLdšÇ¦‡øæ<À5‰‘™âjYøîä>fhaÐÎJ4^›‡Èt”8ªÊ-ø³»lËB¬O9”èŒd’Vobú(ßB ±ûõDkÀ V»Ë2Z–° Ý…%òWÇ}Ÿ6 gnòà²o5>jÖ5yrø@ÀcûÎ4 ,rKÉ"èñp÷›š‡ÅÙ!Ü«ày>nº‹<òŠØc ðè!w¸g Þ+G)ÒjGRX”±Øy|PXM6M^Æ^è*vÒ>|Ë Ÿ6TÉú~]—b<™sWuâ™±W ÒPò2xL‰¢©QýM“œ7õI §º¬kŠÚmJcϹº®:ƒ@¥Ä'w¥¼,Ømo†k±¾®]¹©’¥DC…ë*ÙËžÕfË(øãª.#§lK(MU©Ÿê=Â{´v¢¯÷»éªÃv¹ß¶WÐOGÒOÀ0Àó+ól ö¼G‘÷† ß•Íù´ùU¬¿û÷øÊ<×?áêzÔ+áüÇþàÍð\!D¶ÈúVíBÿ –ÍñwÀs>oÌŠù—Ÿ¦¼Y~ÎÇß0Øéí¯÷•4Hècë¤å•~S.ïÚÇ`ÛÏ¡C÷t\oWÕfYÀò»%ÀÐyUºœæ²­].)Oso×f§MõíÔý¸¬xŽ«¿/›¿Në?Ý_ç ó´)_öâ¢Ú·i´DóØúV…k«³ª‰™h]IHX“\6¼‡Åªðu‚Žó‘¤+T#ÛSw˜ÓG%öªôÛÌ?¸Œ9-=ˆn0rÆeâd‚=ô–Àò|hwËœ>mÛÆ«:x>6ß.Ýa]VÝÄ0hm‘ðp¨3ߺâà¯VìM55„ l\‹=3çkZkE·‰ÊYDö¸w›GöóqyÔœ®»eÊij²å†ñY6~ð„ÆôœElIÄŽ;”yëæ’=L\~#*€ ³!u ÏÝPD§Ø†ˆÚRr›8;2.Qî¬Úvì™,+¾ZÚ©Èe–ØF&/Ö¡šeWàÇ …¾‚ÍLÀÆŒ:f ŸX¯éYX  Ф¯þ)Ù®» 4ì°ï̆3%w>v$zà[w>²§qžÁCÿþV‚”fbDº–±FÆê\}™°InT ¶=Oc~X¡M,1ÚˆŸN"FL.;9JüPöôÑ …àŸá-Í¿2ÉÑäAfL`‰‚vwÁ™ÁÄw8§ÆJycNn ; eV…Þ=ÌãÎ!þFwßjƘ{Nëˈ}¸Ñ’©n½;uf=Pú`ÏÝmïŽÃŒ•æñ–û’oàÃ-…SÕXDÅ4É›UÂî<8&3 ýÚ‚X¬Éx —¤ HØiƒþ½ëL)`L»ŸÐøaÌ#†åæC¨:b)<¸ÒøÖh(f¢ú€çLὬ.ö „vļ#K—2MtvlÀ¥£·è®‘nMWy´,éê—Ú:‚L£w¦æ/6ÖEˆLW£MH‘9‚ù Ù±®Û:QC b2Žu|€I)û:û¶±ó°®Ö?Ƀ'ž‘„Ï!Ü´…¤Å‹d%‰†˜©/·<ºŸïøÊv˂π t(P#¯¢ìVÕVÃoòЊœQÌẾ\á wŒb²ër€ÇDA¯ë·Õ¹fÐcïnÚ¿xõF=±dÕ뛌4PŸDḣ) Ú¡´0h:ÕAj’”˜[@ÖEêÜ‘H§"vÀêÆ7þlÔ1TF¿D¤F§P¦…Šh™NᾸ ‹‹@σùaUJ|Q›¯+˜´—‡sc ÖAß E2ûñ´æC*Ÿúóm[xþ= aóÈñ­¡´O‚^ÀUS„µ(b€'pç1œûèR©[*p'<;À²HyÜ NSÆÎqÛn—Uá$~•i@5,–À*°Ä#ærEâ«oËÉõ'$`bòØc…cÚ: ÏÛ%Òb<î2ñ ‡í²à¹S7ŠÄ-KdE¼n²Š,õ€«Ø<+Ê–‘E·¼o³öÒ)QÃ7‹§WàQÚ)ÇÊÏE×Ï`Yªÿ Ô‘ípñ2:ÞÀ:Ý·Åv‘mÅhV»z]§Ë"b¹à- m Gbüy*ë¶¹q À“Áœ‹|·ivëf·i߉ò7,y%žíÛúÞDÿ+ý¿€ç=ölÞ_ît|oç1L¥W9mÞÿ{:þ† ØøxίVS±Y„¤æÇãú·Àÿ'³ÿx®uøÅêõîÆåÒo½™kÞ£l<ü„¢ÿ.›×ò®ÂžÓæ°m ã¦Y79½/̃„¢EˆãÆ’©Ù ñ›§Ol¤l_·‹ûîïóæe»ø~l¿î?ŽKV$u϶~ÚÖ›:23¢¤ ×¼ì[pº+‚o‡v]ð9'Îër™¹ðÏ_—5äÃy€ŸmÕÑ<“¹ÞÆë ö–HæZÝôtn™¯s4Óï­›H{>v?ž¶(}ô'ëEôýÒ=/Çö´­d’ÐpF§Ê ¶¨l2k^âO¸)º‘EáÉøÊØ©ÔS†®Do¹;ðâë õÔMƒÙqSï;´“)æ__÷QÐÄ,éÁ /èåi¿  gˆbȵ"UÊÆU*Àcïdxûä~:¸£ qC‹ñ¼[$\Wç’ÙèíÑÝæ#¨¥SÒð6"â‘Ô£þº{FbÆÑLìÓñð'% 1è@Úž†YÊÐ’™É²;¢£)–ɘl-f(±~b²Ä輓Õ£üÓÓ4qœáAÁ¦?3¶ :]wCD¢Å7924O\G'¯ôÄ{% +îW{ìC×lÈÇÌO–ªU0 ¹ñÈ¡°…+ ,ToÖ‡g(0ß>º¿Œd¯“Óf!þŽÄ‘|,EÈK·À‡¦ƒ¤RžeV%¶¤mYæ:V½0  1 dÒ¶­¢…šŽY‡«ÓPêÉdb3±j¹wפR„B€:“Ø!ßc¤f®¶Š‘ãt¤ôNP5Ù‰ö”ÒŸ C%îø¸®dðS'ÔÐbÅ„²<ÜÚ"“ØO»æ¼Yüù´=tE£)©ëÜ£Cˆd^†™ÌþéNÑOÑâÅHäõ"·U<@ŽÀ·E[H0Ïu˜ÕElxXÊk Ž­B_°HÝaEêóì¸ÓB‘æ¸i¿žw»®âí[V4T ÅU˜±÷9zfÖi®Rc{X ì(J}ž,;ó2ˆEÈŸ¯nyOxs;ºâƒš:\C>«EfòÆšP‘ÇÝæL¸ks1¿3H¨µdP‘<*Ê6Gr~õauƪS¾Á„ܥžÚ)B g¿d o”ó6‹¼+’*öé÷ë4€Ð(4#·or2$íYc3œÍÐÎa×A;‡}÷Fï0ÃÌuåÕȳÕõ¬_nÎÿ(o$óf¥yÛþ Ej‡Ùš-¿²ÐïÊùz åJ,§÷×Ú¼‚ÐæðlÌ%ÞþúõZ†pŒõfó“vÞðæç%Þ£Ý?®ûxÖo¼w:­Ò[9m.oíùoÔù¹Qç§QëJJ{Ü-Oûî°ixÍøF¶MÆKuæEÚ.þ|Ù]ö ‚6(b hsÿ°Ì_¶Í‹0Oóm¿üºkþ¼¬œ:€çe[užÙ“zKwmƧ”G)Û6¼«‹çýò²©dòâ&yÞÕÏ»Å˾YæE’ölÅ·ðPŽÒãiö k'™Ø³eáÚƒÑi$:#žñóòEÀ|„ö«rÓæüp e¶Õˡٵ’] -’þŠóò/›ÒýŠ»²òæ½PO ŸV’é1ŸBUu=¡ ¢RžqPeHñìÐýÊlÂ24IŒqåx„¦"^ÅîM<A30–{ÚC‚Êlܳ§0ÉùŽpGÄ[“Ÿª†Î­é@sðö¦#àG:LIù ÃèŠ1ݼÂÒrÜù˜ÃÙÓ‘,¾}ƒCüÅù$e ùy„s@‡‚dç'\dˆ¨©’Âb½X/KäZS&Hº@ñ‰ŸìÏÃòt¦l Ÿ<²‡×ÕÈO÷»iËmWIˆ¬7[Jº¼4Óp¾) ™%Ï Bðå$޾5D¹ü$|Î0Æe¿ä£æ':Ëy×vÊB( |˜==C'a<2’ýЕ(5†´×µI#£+!]ÁEg±‚Ζz†ßtf Gœ€jÏ1A$¸1L‰aG“N«ñÊ1©i¨À^G¬KÒuº…fŒ¤ŽÉîh¿ªè@¸ÍB%™>®¬-Èçï?/O‡vÕÐ ‘$.ö¦tw™ÄÍBpK\?2¾{ßÕU £†±7iO†GI¿”HÌIð h ºò*„„%’Ça™„OQ&9„£ŸZ+µ#Lµ_-89drÞ.O(Å‹<öç‚C‘S)Üs}W™iÀ…x‹X¡Ýr1ô…ô!E챬Še[%ÀÏ>•»’'N=éÕ{i¦LËòÅÁ´H¡õ .' N~Þ5²Ü.d¼U[HYÂ3ê´:wÊ9ËÒ¸±$b§JvµL†&I0X.‹€3u6bÛA`›:o‹¸Íãe‘,Ò°ꥮ^×Ü,7(š/…v[m»n¿]šåé°†aÞ 8oœóæÐyežù÷ýžR.?Ë{€y¿óOà¹lOïç_ôò[àùyªÿØç xÔÔóZËöj«ùÉZ¯6œwt2„sÞü¬ê¿Èçÿrõ÷—8ýž¬®~ÀŸÄõîßkýÒŽ¬¼;Ãæ¤ƒ¼.‡Õ~Ót5tÑÐ-PNÛÅ·ËæëyÍJWÇêKuwÒ¤ÞÚÇió×yðü8¯¿:¸åe»xZ—”¯;±ÿð*>më¯Ç‚ЉU[Õé¾£×—Yì¿o“}›þõ¼ýzh7’4~Ò$öq™ÊȯãRÂì—2æ¿ÊpZEóý2]UÌÓäâú얗mËW?y|üûëñϧ=Lõý¼A9¢s;mê—S·–aâãFÒIôì k¦G¢«¡Ã)S—®‰¶A]M=öšU‰Ùfûu%qw\Ž.«Í¶Ë¬-ƒ¶„y»¥ŒwpøÖšÌÏù–ýØs£ëîPt쾟×^Ößöí÷ÃòÏS§‘Ì‹ë8ÁmŠðyò2£žèÄÁÙß_÷çÄÿõ´ýó²mž6UáOªpzîòo‡ö+µk¾Ÿº½ #µc{ó¬*™÷¸*ÿzÙåàØ™ö{Ö°÷÷Ëñ]ÝcW>ï—±;.#û;}Åe ¼£?6y"d§mÛ–]S&*F¼m+•ùìèèê.âÞ™'#=c›øûûÙDtsxpÜÔÔaÛÄ,M,ô"÷èNQå$Ö.ó}k°nRúØónóœÄºžñ ›œœÎ¤§1os.gOz…DñM5÷Ëî%`l'ÀЈÕ0ðTŒ"ÜÁ†k°`Œ}™%_}(f‚lu@HAÛp‹°‡,çÂ9£ù:šLG½éH"rù)ËÉ`6îK¶ºÙ˜¿Xg™^’ÓªËÐ+¹twbÙFv³ÑDA³Ý Š'Ç^QŸW_­OðFÏ™˜D=:¢ªúcøGñ`fn%š^1 œØ·9ZQ®&up`6I"ö®ù@æÒHNŽ&;-R7‹ DêH‚d drm@{¬qJCÎOûÈlSc™X !¥Hä³Có&RUlQk< ¦ã´3ôF}ZOf§âTô~eŒo¡?¥D,Åà6´ç=]gû$ŽmI!8ˆã rº¦@î8r/³¹äƱkÂù]{67“dMÅ2ÃE5GRm“‹G,Ï“#’HÆÖÈŸ}ê*a7(ˆuŽe…c­ùÈØ‚¸øG-Br8 …šŸÄ®yæY„׺Q±V8'A7gEuyîˆ6ä¾'ÔFÈ5¶ËYЉߪ”Éì1 >Û¿CÌ>M~Ø4ÆÎƒtƒd@}A¥s'™a‰Ø¹Yqéñ‡4¬òàHKÞÝç½P×”‘ò|\AAÈAïJaŸã¶ÙA\èA-üJ8âªÊU‘Y·Ù~™ó…’Ox¿4#›«ê¼mØ?íË~É!Be:°:wÏévBÖ&7FÞL‘,CÓ7x&¶ŽÄsD‚ËËÕYÒŸT´œ4 —1ãlšŒ ÓH÷¢a3ÜÊ—‰`‘žk²7²,486 Ú—]+6ä2x>¡“Jìñ˱“¹ @»L³(ËdèA“GëE±Zäy(N@ø„:Dî´–K µÒ’0 e¹ÈÕŒƒ¼ÎMòä"‹Ò$¨ÄRQUEL«³ˆ²¢9¨bÂ9"êTê\&´S©ôWæI…yÒ@¡¡9°ÌBÖMGÁžëeõ|Þ‚=0A¦¦”".Ô"2cêÅõ&†”‡"ºªÐ4O› !b–ÏaœVI¤Sž‘ À~:m÷›¶ƒ§Ø= µ£L ô{¡ˆî?Ï[Þô d·ä0-ú$ˆèëieÊw-?àŸÃòi]Ý·¼~¼uçMrpgTèò—ƒ éBÛ¢K/ûöÇyu^û&‘áð h<û kí$¤ùi[m2rìŒvK`é@•Ðkž?DÎäißêຘ©7‹Àð±Psêí,Ë„Î!Fåñ­ãº¡ÃDR OØ–H†©(h¡BŠAúvæýýýD ê ‹í×5àÁ÷ˆžør\Rà µ %•,›TíE’y•ÃiÙÎèxÆ*‘I÷¸4W亞ŽM¿ç[£ÀG.°1ì‰Æ º­(×I%óVȘ_3}J}.¨Ãâ.Ñ4¶jš3£#‹±¡&ÑÓX\Ì5Îpwn{ö̵ø–€€„)+¶€Í”ŸŽºiX÷¹üœÃ0sc2®êÐéùež9¿òtžJE^Ì8“ÛY™Ž̬í &$ªS±DŒ3Æl%`0)3WýG€ïO5ÜEvÖT½³Ðv 6Z4™Ü‘8ãÆšïΧ5ĸ$“Z20M/!1“ÁŒ³±Ìb*i‰ù(&ñ$w'§ªr~ ¡™)õñvš(Ü2ó9u0c5eñض€‡±ÃþÖDǹ8œŠ7)ˆMhV{\æ‚@e¦†Ø–‹ìEy0}™—j"N4ž‘#O!=Î<Îy”g¤xlãŸBÿþíÒL'CÞWàd6¾•§l t†G]™›á]¾Æù€:òSíBü%Á?Â?3ÌC}\M‡˜%žŽÞši€®¼iôæÕ’¡(â¢òõ%´Å™%ïr3ÍŠ’yñäßÈFð!à ö™e$a?Õ5ì‡çËá[È#°èpÛ–ÝÔ|” h©Ô êè¼ï@£4œ#ûÎPÊf±¬Eå難Nþl k5ˆ£éR:«})ñÏß©Èè•Z\ÈúŦEã°é1(UâÐ3*9î–Åq³Ðáð’U¿8>‚!Æy¹—ó®Ê‚¥VL=V3€~€¨$²¥©CDI5̤äi`ÕY!ÐiÈ¿edÄ÷N¼6‰(Y:Ò‡O21Á“Ë*¡7`zº ê)Ž-‰üiéĨ<ýÉ—ÝqSÁ?(tÐÒæ]ÓÆ|)öd‘Gœm½Èç£! WרfxLÙRë¼]¡Òæ²D©áD|‘,ÊtÓ-ŽûÕù°aÉ¿ X‚|cj¤¼0Lº[VTõù¸Þv5Ä¢±ÊD’…y´°Ý03<ÊOgÝ–¼l<íW¨Õ4¬qßÓ´*M'*'°íÏ+ŽÍ‰¬XfÔ0åš,…¦Ø ð e› Aš•Äó€7€G8Gוvd»¤¬à¢}##Ð%^½É÷ÒÙw—+cOÜXœ‰¤ ¦$U™R¦au½S¹‹®-wëÆ8°„vö«ãAhç$ú=œü¦üðÂ1À³7?ßÁÏþ•y®;¼c$Ã*¿9çûõÓ¯¸õËïjûzÅÿ¬¿0 |ò´¿ü믈òëþûÆÍ>†î ¿¯îÎÿ}?M7Wz3aý¾\Ž”Ýoþz3Ž„‹v›F<¼¼Þè EÈ×÷õ¼ùzZSÀ ´ór\ñúId¯ŽdD‰.%_G°ëŠ p¢ä½ü8¯ÿ~Þÿ}Ùþ8®¿î–ÏÛö;pÅùs¡ú“eá?íuPgª¶¡àùû²ùºoVhsC“íÛüeß|;-O«bצ»vªÿ|ÞN¢æä! A}^ ÐæM>®*ô¸}W(ªIwÁa-¹zøêÛ¢tX’_Zè‚Þr·ªWM¦~Ñ"Õ.š Ð"1uLg»n {:EN›zÛJâE}gìÉíC;uîƒ::"5ÕÎMÂŒ³žRiÌ]`Qʸ´ÉJª.-ÅPé N:R$2µ2ùODþŠûÀ2A¤xbÑÄ%iEbM½kÂ%a¤9’Bðm ‹õ™¯0C „gÀ’±Ú|F<üœM¬©¬x»ÍØNá§ü5Mà(GÙeªÎ2–t‰wjK–ÖCa-8$@2ŠtزЈº9 ¥‚¹rˆc˜¤H]uÕÍ4âe¬aàÍÄX¨l1qQ¹áoÈL ò©gåq ˆt|t0ÏÅ;c›‡3Ч4¥¬%ž”kü’ÄíÈÉ5‰îÈäŽ31<Ô9ÔÈÀ Tg–¤,váËwçšúXòîFš©O"‚”?Õ‡ee‰Sd’Ç/OБOcxXoš4M\&ðåÌÒP?ª‘ î\¦>ŸãȃpV«úÇ÷§í¦ÕQZ#–mSØšŠ'Hha‡Be’ØO%>Ù…ˆ`YOSú?ìKܲë Û9IB5 B¬sf;f'¯‘|qä Qo:qTejê1¹zX‡sX_µÅJc•e}Y"R%’S¹HÂB$Z5P‹ ´RIŸ«íªâ$•¬V3BÙÊ[Ä©ÖËBýb‘ênf)ååߢËïZ>@–›e¾YfÉj»m/‡ú/À—ÃRLµ6;ldºœ”ÏŠíßøJ 2Fž"â„Í’Ž7ɸ©ÐB”ïÕmÍíxêk£Àc—ãš“èz ²EU².‡Nh'õ öèwËu›ÇòBÊÈÀ†¡æôœò‘0•e)£ïÕ„…¬çÛ§ÛÌØ3–ˆÄ´¶“C’­Œ™­{^ˆš‡Îè´­é`éÙ¾I»Ñ0¤t)>Ò±=!¯<íaú(ŽBŽ›øa öËW ¬¤ÐÏ—DšeIðº¾ÛHüÉ·çò‚„v$ÙkŠØè†”ó®9 €°P€x¦]S”bçá¹g¹š€¸ç[Öç±ài^E@ëDd Ïóâ}¿ì`žç}÷r\_vK>@: T!T™Eű±ŸvÍçÒâÇyó÷ËáÇ zé¾Vߎë§Ý’7­¤7ó&{±Òìé×QÐb$ ýCJ˜´äçÉw‹äe×¼ìÛ“5=æºøz\žÖ½X•ó®ŠÑÎø"@¦oçÍó¡;Hø_¼[f Úá¶Í~xj§ÓÓì’¶K#3e´£§¾IrëÍ8Ý£$q'Yä¼MscGH@ 盆%Qˆºùb !‘ì¦Ãäcl80ÅQÈ¡øöìñì9cMÄ&ü#?¯;êË œub*O¦pÕ@Ò™¨ã¡¶j÷°Ô¨2eK­¯Š t1Yƒ`™ßGÉ$Ò¼‹Æ=g Ê‘qOR˜G k>âB&VPSÁDiÏÈ\Ú 0‘Ñ4¸7åä`Fà%Èßy™{ɵV–Ä#ÙÒ8@] f+uÊÓu Oª—PXQÆ1IKB;€œ8IñÝ™ žRk†p”3âîLs_æZEÕÓHrÉ’tC/›uÓ «‡.ücÍ€»*Èd23Ït26”R•iÌpYIYGÉz)ë°“mÓØ7¯P`Æç¯4 t”ºØâÈ|Rß–ˆÛ±ÆbMÒÐŽªˆøV×Hêe ®è ¬ÂØsI³ÖH0†-[$È1š”:µ¨ú:}„àÓq£ªq´\¤«6?î–,SçÂÙJd"KèˆÚD‚L4c!yØ6Æ.$#¿4"ü`hf€:}`#~œãu†wUl]…hækBèƒIúçkÕMŒUõ¾ÿ²_ò©ò­=s8j*Þ+ª$ó‘Éû6çÖ BSÍtuzŽ˜qPIÄTUÄ«Fn%¾e߉K«ˆrq€J²=—ˆ&u´59‡#Ó©›±±Ï~½`7NåÎÆ¨?ÐÑ3åV‚·m.fvú.¤¹yОþþv¤“uèaú(£L5`Iì¢ÖÑWÓeÑÛ¬%D95ƒ§€OcÕ3ÖJ;°G•Ç‹*ƒ·y1Š”ÇQmº¸²[·Ïˆì]WKNB9êã<Ü6Umز—Ëþ¸í áœWóëΙsº%OŸ+Jû´åù°^ÖÕ£#¹ðÉŒpGËË#1ˆÑn›â´n.Û%wñ´ïèÌŸ+)ûîIc5 íH‘ÁY5´£œ³0夶³ÚvËRJWíd@–šýW5äÃFVV5/¬›sS@/` õi|5œSçq©ËEÉq_pšxŸ»¦DVí7Kœ|ܯ”Ýêxب0Ýk‘c¹âÁ+!˜ò_býééð‹G ÇPxøiö1+¯LòÊBºþ´9»· üÃöky;íO€‘z úWyÏHWSÏ•Xvï 8¿‹þ¹å=çç-Ÿ¯–«Ý”ß{¯~oC˜Ù=öÊù­\Ïszç2;ì;–^ŽNŒªÓ)u¾ ?Ÿ½è×Ä7µ—Á§b!¡#2£6¤d¼ç.Ÿ¶qCóI~?mþDº‡I¶íV’¹µ`دÙ~R°ŸuäïZGu!‘ª¼x»U}Ü.%Ö‘Z&­ˆõì¼_éè¤ø°©ùœùTéŒKKƒ„S@E¢S™\HLÜ`@"qMò†Ø0õ¤b\õç|Œu#H³(„1ä^€Ì,@â$—C÷t@‹›†úûÏB.IÔk‘xˆZЏrª„u+Žrà-•™§,ªÀÎbÒAk´; Uÿ*_ —ýçÓîi× p}Óăû®<8®Å¤SK>ÿ^h¦jÇ–8™VLyð ­MÛ¶lÓÖ9Ϩk+Þ!á"9(À⻉×Ëê|Ø”Yd\“ @­»Õ‚>A §íQ‰ˆv1§2ÇòÐyšµfûá<\‘Žb6f’´02†&±ðD^û]í»ú¼]ž7íiÓ>ïW0ÏËaÍ-Hù <ÍkQ3ŽZ{L‘uúd¡ÁÊQÌ; ã•xÞuÂFŠ¥ÀC‹uµÔ¡)p9Ë\ÒGG@· E­SP‡B»:4xTÎ9j¸Žqc‰aGäéå`˜çrNï,3;Ã'¯Ü¢Àð´ÿi`ÑuþzG5†sÌ–ƒY1ëú×á•…®Èñ~Ý`Ã+‡ü4ý_Ë[õLeöÿ/?Oþj˜Ú¾ûww9ÿ‡3î_–+‡o7õîç¿ËîÿŸ²:SyçwØsÙ‹ñMG‡N[Q0eè"ïmÖh@/,Ÿð·óöÇÓÔÑ"†SIUŠ3z¡€Dç/Ì`U(áÖòjè>í¿ÃÇÕËö¯—½8 ‹à´]èt̺›UÅ·Æ9­@ÆÌ ‘—çc»KI!õ€b |ZÉNu¶Ýè¸ {…€ŠCõ¬ùŒ¢?mŽ­ËŒ@š, 9'‡G‚OI8\§u€»$4(M}sòÙð¦>R>OƒEõfäuBxe2¨‡›•,ú+è}-C#R‘û‰yô9¬•°?‚%; Æ:T* ÁÞ£Œ°nOšvLÆIµE«©öN»%^S§<ÜH%e‰OýÅy§9d6ª.‰«H'`‹ù<Åï¶,K6(q+¹u®ÍX$㧦†µÖ‡ÊçPY®¼&qivœç¿ÄÂcÔ«ƒØê‹/4tw¡=£üëy/ˆ¨N ö¤39ïÄ6‚êwÙµÏ:¨ŠÎGš7‹Ä÷$…æÒ1ã4W)q;;ڮ̪BÜ[PK#è7]Xç±–:¤KÇ%búÈÂã¶»7çÃz­.ÃZâà¨Ô«p,•S±]Ϧ†!Æ%gàÁQðiZÄ>­±^Çu{IK›Õ×ãæÛi«œóÓ¶s^ ºêì–ˆ–΢x¶’çDøªiÎbâ'»uÎ&M¡äsћ⠭ ð´Ê3B}M)®½ØçcƒU•¶‹bÝ-uÚ’&Ú©U‡9Ö<ut4Öåü&¯ežË¥ü*²ÿž÷°ôxŽïþýíß_H¸âÕ"ôñŽý `®<öîÀýÿµüb‰úç…^žþ?öÞCÉq\K×}ÿ×;{ºL¥¼§D/Q&ëþ€(efuõÞ3'nÄ™ t6E h°Ö· •/€§O8ŧ?lJ>Ÿt¯ýxp'p4ðào¾§“C~µz¦Þk(zgÖÛ¢«÷Ô(õž?HZ:5¸"÷Tn°&&¯93‡×0ƒ ÏÛS h_­g\F~,}©h§÷žùiÓk³C9•›#héõûàÇ¿–ã¼Û8êש<k¤ÇÓN›@2{¬‡Ïÿêêm¾f"t¹þ:Wøˆú0ëÚb ù9€ÎÏ`·N^ÐÈ@³Fú¾Höd;™àªOÍ®cM^7sÚ˜J\˜z5yïªcýºâÖ®¼8­S¤â©(&á(+fMžaË8Ž?åb\ÿÒrúÿèðN‚ Pèèžbýo[К´Výƒ§¿PjÐt(`$Ô2¢B÷UŸŠs1%f6,÷\·¯BZ£—çÅSû1ð,é—£7#ý3Ôîn»XÑ™ì7 œËØÃÌá3¤>b\`̆(E¶ð0ÈÂg&ú%˜gÂåì`•89¹É×üeLe«:Dà Rn·œ0}z4ž~ðGÇ–óºÎCÖA¦1JÅiÔtÂy~ÎèÕóÐL§S›{ÎN tÁÙq $œ¡ÏýŸr#òðˆÁA­E3áÂ5/ßsX¾†ÐÈ„OkF¾8yÁ³Â¡»³Åpòò×a¿¼Ô™’'€ú™N<çj tòXíU†{ŽÛ»šã¡ŒÌ!xá“7ÏÒ‚*ŸŒß€Ç¨>YÍFÛåÔSð->ޏ®NàÂcp6ª| Np#ä(ÅfØ¿d^ÄËP¯ >âÁð'ožêÎÈÑj:Ì7‹Š^þÖÙêXdçj¿Ç|{Øuü·É蟩•¢#ŒY)Ÿ‡ë˜Ì ÎA)åÞñ6˜GY@ÜY†ÝâÎŒéÓ4äUðJ ´â6^¼!ƒ°ƒÕb²YáÅ[f`žÕ”¹:efÚ ³Î‹±S•ЛEÛôQD¥uÉ?)$}ÛÜok£mã±?–iƒÛDêG”J¬ÒžúÆTŸ~UÔM^Û+‚·F¾î?¡HÛ<Ú;&¹ãRåöŽÐŠtEu‚Æx+Ô«ÛýÁMn?g¶¯€§è—&ôÇ‘ÇTö#'Ml¹†Æ„ƒt ‰1¾v•€g×Õ;üódK®ðÔÑ\ såàe¢>, %ÖûÅ¥ÝuåÆÌÓæK¦äÍß )RÓMÏçfÇWОi)B€ÔÐ ,þæ†3F=Æ5€GÌ3=T[È™l5Ïlæƒ÷®h‹•’ÜV¿Îµ–ß™ÎÞžÚ|ƒ¦Þžþjö›ìòò1\N`8€×8°µÙ@ªéÞY_%ˆhðô_;’CØGZ§*d8ð|Èa|Ae°¶–#:^ßí—€b…Ü(w« Åo “rI<)fqjúÃ5ƒÃ¿Òˆ Nh¿Å%ÂhúmiÒ¾)?¹®);ß½„; «7²ÁHÞž!ƒ#?¥2¾û£CCz*…ó¨¤ÄÔ¢‰ÊOá»QçE˜A!–Ú°ÏGÀÃ,¦­ÒÑ4~ñú-MgæìTNU~²OcHÏO:™†1ðà‡ÓÊ^H;|;~ÂzuèüéØ\LOr6ÛiÞ+*x<À’©ÏN,ñ¬X¿{z¾)±öûBQ*` Ï5­ø\7¤Ä¿žA/?ÿò»ç”’å|@”b*4>' AÚãžÐÉó òÒÿþóû_€'eõ¼½ MB#Wió¯N3zÅ'bÎqVÕœ×þ̼ç¿<ÛÍ [¬•óƒâWÚPŒ 2Ä3Å­{RXóâÝc‚èbâhÂn=‡öÉä:ðü ˆ lƒpö-æQù2/ÛL ÏR|…ö(ê´q:80Æ\·óí#}Î9bƒ•¦Ià‡ŠJ|„¿/?þZôbU8 D\ž-hqÒ÷ëèõ;Ð}{ýù—×¢a÷–“Í‚â1‡âÞã‚x»ŽM®$¨‰ñÏÓð ¡)tÝðâ?Lzý>׊Ä×cñëT+Eç&䱄,ÝÃ.ƒkò ZÂ]űڎžÿ‚Ì9 tOªÉB¾ÈÌ« ¹q˜š2ڽͩñÁ‡Ãõ|‚û ºÌÇjÿù˜«ô(É/>ŠÝºƒ¥œoÑ þ®ä-™_²Õ ´‡–ñøŒI8Šþ"ðÚ÷ç%.Æ€t¸É·õn |(²v¿êtåNNž]³[µû5ÈçX0wT Zê ¢c[ÎnXâ£Wk±8!p¤‰Ÿ•öQŒ¥;ðôB5-¼qêiðüÜ ™ÇºÞ:DÝÕ3z‘;å§=”Óì}*å˜ÍÙÊOžtü<ÕúêžoÆþ>²BœŠj1´A’=-眘 à~0¼5`ˆÇÀƒ¿3ÎÊA~ÌeDÃ3X)™gÌ„œªMT› x†¯\tQ†WÇŒ°½TMtÕÝ€ÖCMŸz¦S+ƸŒî \Pgø« ˆè™é²úí;ÊxC ˆ_À9À90ÌOÅoÀ$F‘@>öHðVŒ¡©Ÿ¼¿ÎËÕH|WyŸ…d¤f4|ZÂúÞLóAY¬¶›Éðí{YpyÜ×çïw o¯OôÕ|gN€äÇwÀ>á`~¾ ^±n¿¼ žþø†Ú¯Ï¢ §oS°ÄÛOl¼2-çç÷ïüuØ?}ûÁúß²l½Û®GÃÁËóÐÑ|:äûƒ;€¡*ò™Œ^<ìØË÷Éø5ÛÌ÷Ùr®YÌØ^ lˆ:Ó7Àvbc»žÉ-à‰9°÷Ã7€îÃÓ\i»í‚K—læsÕŸ&ðAÈÊFÍ5ƒq\¯Ì7Ùfýbªq®©=/öð`cðĨÖ^ ;s˜ì×òy>Ñ“€aùüm¦Ô ¥–‚¡§HQù ›+ÿ§”ˈm®¡ß_«ý ÃPÞ˜7fùRˆäêbƒfyÒçol54µbg07ÐŽ­ò H†\'Ÿ’5‹7UFÏÑ ¬©Ù¬€«~{þŽ‚bÄ¡W£x­c¼ÆŽym¹Üâ" *ú†g˜){óÅøº´àÕ9'EL$?yTëC¾iwkºz`TbäS|Z’Ïê =⟵øgÝ#"ç9o±QÑ·³Påë9Ê—3,ñFÍF«ép>†ª‚b‚¸â5Û¹ÈwëÍj¾Û® ­v€œ¢È`îVÕ¾RZrUÆääÊÀuYÞJŸyú:º®ºßY¶íƒNï7òÈQJñá¨Ôfÿt¿«3‚ŠCD&3ÃáP=´™j~QÊû¨Ü­ô[蟽Cøˆ=¼;¯caiO`žFO羟÷Q³ÂÔÚðî¶:ô è2*c-ȼ'`øüºNÍï$© Êð˜a©üÇôºïe4 ’óÝÍrû~,ZktŒ¢õ_ñ–âuõc°Ÿô3.v#·‘©)Ö ð&[4ùºÚsÙOt ‚Xhç|ý ƒ‹®à|fiÐ5œ}ÃPs äÄÎò!3ݱ…09–^HäðþŒ«å3¶Y·£|[ç¸'¹×”EÀLåO*:ÿóõç7ýRÌ¢ ¯ ~â+°–¦=¾BNrÒÄÛO-ÏÅlI™#.áôšhHn8•Ci™3ù1ž†Q/!«SIžOžõI 'â\ˆ7eC™Øø;`¤€6Ú›èHzoš&cÅÍ©ÇRúœ©7~y}úfˆ2;15è雡k¬¤Ð—Ÿß¼°êRH¦x“²¹†òÉœ“ø =áb–õð'ñcLüpŒc¬Ì윟€7(èÃjA• ŠP;,s-I‡jÚæò­D2EÇô5±“t(‰²xºÑ“>>»Ž[ÉÅDÜ·'óÌ+]^ßcêÓ˜¸ˆøúÌÔƒž±ýä¡ ®[2â]ý9ÜÊ—e¦æ™ Ïßqx¸í= ®íöcµG`•ïU¹óì2&Ì ÚèíeIí0 Ÿç§ïÓÉ4ÇëËÓÓÏ?¾SÁ±o“ññ=˜g2ªÂÏ秨.zzú>¿À¥Ó×õz‚&x°¯Ï/ÏOOO?^^ž†o¯cfA£'/ (‚ÿ±€4/ÏßgÓÁf=#óPI½*–Ç%éðíz5]-'Sm~0¢°‘ ‚;· P‡ô;£'Å~Ý1Ó$ 4 ’AËh¡ØC§05BSx<Ì’\³ Lì=Ííi¹ß˜/JXÒÜÂÀ X*Û°36evéjσÒp)‡Ÿß‚¡ÑÕ@gÔDVÛ@‘ZgNEFShÁ±0 Ï˜ÆØÄ·Ê`¡Íré`ÖC=2‡ r¬…¥ŸoÐFÄšÐT5w'uÔË‘?Æ3L“îý•Q•T eð\û¹]³@£œcOtáåRÂH[ v§K@³çkzÎcìïˆ0#|½üüüU 9y(¹ƒ {õ!RPù×{wj‹l5›1Ÿç5æ <(«9z2#ð0‰ezôMÃ]ÅGìì7KñÕ°Ú­ÑZ+?Ïz6^N‡ùvéĘ٘͢>ZÞ.gzè0 gƒ¿B‘tÇQ¨ƒæÒ4;ºbÂ!pKWîôðl°Çrøx\ÆÂÈ9ÊðY¹Ž“Ÿ:è8gƒrò†î¹à9 ,§ÌUð`—æÞî¶K0OÁõw`0€§(w%¶¹¨K.ì é:m[=@ŽJ¥{sVz¥4Zü‡åŸ6Ò˜* ­¹{ýÿ¦þoJsïòò†[{ôƒ5ŧþœþ~Üÿi N›?èçç_ðʪÌÀî“g™àgF- {g ù€½€„чrjsˆ \€"Œ\å*¦¯0=eàl¤_'|ÂB ý3PͬÒÀ•Ý’3&V“FD¥”¡šžÉõ¥åJéOÃT©ö /†“u*9Cv« ,H€S“iÖ‚ì—siäe4Ыó¡óÀŽ£¯f9± ʼn!Ù¶°Ë¶Àª-øÆ ® uj­—5ÖH¿œë¸rµ«/Ç •׳a!#íT¼Ž(êqØtžŽa»c©õ¸2ºëg[±“¦@;8•Ýí•°s©„:˜fœ[ªÕP¡n˜^>£†õšð•×±î³ôÈ+•©¬l¹#¨[­|¡Î”vB¯Ctñlw](¯O°C‡s&h=¹p"ƒúÑí_ä1zÑ #€áŒÆ,3Îhæ<”ù4OcM‰¨ç/ú1&†9Ü œr (~` ‹=E:~ëùsÂHÙ‹ö¿­—£ÍjŒ¿hu¼Õt¥ä"#B:¸L>\iŸ„à@ôÁ’u¢/¨ÅžÑÛ+±§ÙX3ÁIb÷o¥-ýÔ´¦gÇe†¯O/JWð Äš?À'Pœr1k‡ÀÌëÓÏà–§Ÿø ÆA~¯ØÀÎá¶9?Æ ­'~K\y}Vp,˜5Ÿ'“Ád:S1Ö˜h VzB•áp0›™°1HR<N«DèŸhy1íw+\µ&mýd"ë˜3Ü<öù¼é#!ƒ=¨¦ÛÒDfŽq¼*̱Ù.(a"(g‰À?[eãlI>ÏZ3£íù™:ö1ÄM ù,4ÁYð(3«êf¿“€dPrÓR¡)(aĉ3,:‘ce¾ÑpxB#hmµ ª9·V/*}S%E³e¼´è?'…åhn07ÉSÒÐxC‹“¸_ "µVðGÉ/ýÄŸÆÖ²Û%0`!u!0ì ¨Ëìíb‡ð @BSNñMóõÊýWà¶Ìé3°)íg¬‚X@¡m…G)±sŽƒ~²|>z¬ƒPol%o‡<×*”‰­LKš–WØ–KV@˸Àó¶êãz^~0]y1O@>€Ã!)eFªÙ®æ€â,vïh!’•yÆÑIÍ7aþ¶óv|'¥£µäƒà¯†Ÿ©üf#ùyÖ,TL„ c‡.¤tH8„6A™È; é:8-%Æéˆ›½16|Þù&3Ëà [寇ºœáÛËÓ?ÒÉóÚÁžÑÛÛt2¨Œ€©/Øù2f´è ƒ:¨9F¥Áë+8/+X%³PÇX‡Ã×··Wœ‰¾ÑQ–PEJ ÂÉ×ËÞcèµ1în8˜yòVæÌKcfÑè•3ÜɽÃñè…/§¨’qç';àÀWä8kè &ÛlæŒh,&hÄXŽ÷ÊF“72ZC3FÁ¨O'P²^&Û@ÈàׇA›xFkFFF[%‡ 0&5'„`sJ—“:ßb'O±Y ¨ß0×÷$÷•5>N8Ezèù8J°¼pÐųŠ4Ta¬1x³ÕÌI¹h?°[ïQBÎÇz‚eš bûñ}@ è¿SeNØÕÞ æL¦%‚ëebö7d±‘ë Qg AŽAV€f ]!ܘ@8–Ù âe!5ì¬CI9†š †ç(‘–† "»“Àid`îå”fšôjÖ(Tf_ÓTïêb pÂíiРìs¶ì¡Îç÷ù¾c¿³]`àã5¦{‘sFÆD£~ ™sâÉ• ¸…=ÝݵùõTc¿WÝ„ú¦|{¶‚ffãzŽ7p£p³—(—sfz9”J®”`ÃYŸ¥³‚ESeò±¬= ½.D\:L,Œ)‚ôëbÈ4û=§S¾Kµ9c†ÀÝsè2ÍÉ…1¶¡¶·‡<‰:\äD3mÑ”s2Wša¡›[ìKjÌ(9—šFaƒ:Ù³3Øæ’¨P²ªÑ|†Ú…XóÎ •gŒÎihÓGä¥'8)†3 1ás4–÷C¡W0D´Å»½ÜòkA3Î#{ =yb²g!™vD//kýĤ=9ÌÀQ%táq]Lm%/Ñ™€Ž¹o"ú£Ô¦rH£l3ã²xë)(È!°‘¢`Æ'ó’M~»µysF%³ŽÅ‡¾QŽU½>“vÀ*°Éñw4xö¼½€‚Œ( ¥';.¸F޾¢ºä7ÎÑžð­›p¾’3qKSp ˜6€O#þ ,Ó1zÅùç³é„zy~ÉLÆ#&3Ëßòúò<œŒÌÕ:'œ ‡>C¿<š{5í¼èÉ^q®Éˆ€†¨ @%Û®”V·Ÿ„ œWüÅ6@hJª¸ð…p‚S/B0iH¬½-”S‚¡qQ€C+ˆ~RÙË Ü‚»Aà™Ëùƒ“¿@Ó£ÆSfxëþ¹ªœí+Ï~!*.ˆ"‹ßR å„«îãÕÅ{¾_ãåÁ_  Î~·Üà ØÌ—Ì‘ÐBFƒôú|æú¥œìXJ #ø”)dÈ¡§h5ÛÑI2ÓÊÿtM 'éUÇÍ_Î'«E#¶ ÐI§¯0‰š†‰ýESÃI%H#Ï/ÆJr ï!ú@ëd`¯ÎÞÁ6,¹@ýR3›jÈ,t[7áÒ5&14ÅîéŒî-iG¯8¹Ù“7EµxoùÚsüŽ¸Ð ýW È.H :|8a²h•DÜtÈ(U=Bd$z¶ÐõÅ–ò íŠ!°ÍFÔÔ1­Pm 3|4ž˜=®_RàºÊl^ÍZÂ=ämœOW \/'[C6ÂÒ\äÛþn±W·œJ¼ÓDÉ·.d›&?Õù©)P<ÕW<;Æ›ƒ•åÛßJogñÛM£]¦ óv2 ºÈ _óÍ’l3ŸàïFOô™ÞRôðVÈŸS噋;Z’Wœ#—Î xbIÀshë€:PÍmÜøÓò)Kô çK7ÑGÿ))}Å*b†úk:züö«öÿ9¼õ};u¯3ŸŸôxl˜§×ùú+ëwø àù­ÏªODbžªÜA qâžbñ-zû¨4d ÊÂX»©ú¹¶;kÆAeÃRH`ÜÑZáИdË ¬fêÊ1‚Öü+-h…AA,Wx9 Œú¥f*ÓxóÄcÿ †„ƒ'K2ý†Yˆ×è;åÒá)Ñ”ÙV.”½R’ø3ptù.8NÖ¯HDkgp>B¾áµÐß²ð¬ Èø]•™ ‚Ë×ûí\“[áZdk†´ô[3¯8}Lªdþ½RtøÐ‹îœÆ­ðÆç]qÍŸ‘R"gvï6 háˆ7Êi[Œb&d*Y‚NÚlµÛŠ\B¼+Mt’ȇ™Ô¹¡@‰KãÓ¬VÜa¸”ÿ’m!½ £¨© Ç+ 00ðØDURÄ›“B=ùw®Y'3eªß·РÔ’žg1{”¢™œ0øÖ¥„7œø<9!ÄPyJl\3RHe$žö7È6ö)ÉG$ÛÙL21áKŽq Fhìyur/yØo¦õI¦ôÉuœá@§ÁXÔ¤ÛhTS|mj¿“»JŽ,÷X´WiÆ##Ä|Š@/¯¦ãvÊ3@Á@`•åbRbÐåÓrÆo€“-'À0|5¢…Ér1 ‡äùŽ†Ø³„ò¿M§ÄÌd…£ þQÛ“Épå…K`fSÐÔr—­èÌ‘cÅF†ìçt¼=jæpf “%#Y«|ÏEõñ¾á/LþªÈÎ]³€¥ÝÇ|‘ASíKH Æk8« üã =lË 'D(“y •ºšå„¥¥Ò•©C=_Ñ>tS.`ÀG®R²GA|ˆ°6"GåÒûÏH šð0Y„f(›‰ÈÓˆzóµ8æ%TΖI5Än¨²¦/ÑâÛˬØP\ oK÷D÷Ðá¥Äú1„Û5×ê6]^«YÉÜ<,v7Õd'(~€Dh¶"‰­x[P!—S òòÐ~l¦ Ðÿ<£í¦È»Ââ  = RÓ!p5_*Ó¦Úo‰pQ¦ý3û²ª=½. NaËL*UÞ5% z…–gœ(çï[x|( lãષü8WoéN Go$0 j¿A9Vûs ažŸ™«YyûXî~Z‰pØb‹îäªv.òó‹m<¸êж­p™w–xO2hõv9×ÌúlE™¹Ñ¤3¿!%ˆ(E¯r<”}]æUÁ¿-H¦)›JÀSAå9ëX…Ú°ð$¼¡RŽªÙ¥9¢pO؉í^ù iþ¾àØOKúö¡ò×í4Ÿ}[¥®~ZóŸ´ÿ›ýÁu󡵿þòöv]8».³é3Ò§ýïµNñ‡>´÷Áx†'¾­È6“âY¸øTæ@>ÒN̸–Ì"ºªl<\™‰ÙâL@!«m¢°>?–v •ÙéPÔ“*Iz‡äC˜¡]¶Ô/ôqÁjM0¡ ·XÎà/=ÿ\Zd&_ýÊL(𚃹š!QÁ¶§W‡X ?öLfX8}Br¿…¨œÀìÂpÖ~Fá™X;00!5ºÛ8Ôq„åÀbÁ™Ç6Fosè}VæBR. O#Z8¥ÄK©9y´¦öÀÈ 0™IÈ/¥uÛiwÓóc_ÌÞeÌ ÆøI_ò2Íù; ‘ÏÛBqŠ%¹nbÄF€WÆC0ý„Õ<ÅžÙDk¡àstx8@”o»l‰¨0akøjnY-—“1Ð\7Û¬ —•ISCeû€ «€Iø<ŸMPÀ-ÛÍj «| &‚öGø wÕ(Ř‹†s&ä‰ÂA¸Òé†q¯1NtêØ>¨ æ#áÝÝàþð÷µ3%mPÐ ÈaË$"5 ÿnð|ëj'§W¬ùäûõ&†fÄŸ8/¦ÈlRÙAÄdÌ3´éunñVo$âúoبùÓ–\¥‡y&À9‚–Ê­]«‡Z¼Þ€ÜR¨fDoíðAÙgk)û…èµ`*Û“šxF(MŸ§ðdíŒ3È0Æ—›…ðΨÆòUnh{r6ôi3õš9Öÿˆº‘X:$bIø«=RÓ¥ù”©º÷Ù­¸}u=…ê|~º/BŸxÒ‚óGoÂAaJºøÈÃ{Ë%ŒM 4ØéýÜpQ¯r«Ø|§•=öÛ¹Y ſ֧ì¸5-¬íò­ÑÔŠö g®íù+-• | .Ii<5¹q ®ç^yÌ:šÎv±ÂÇÅ,,M¶ˆá¼Ë™ú"®ÍNáé†Î¸ úÖNÓ‹’=&Ê» ó¬õ{Íž“²U _©òDssøÑ H`öæ,£mœÚì³0¹Çnçš1½< ³‡˜Ï3õT ä Z0u‡¶6½¢\”Ý_Tôº:f&0oddÜJU)ØÇˆžÇ^+VWàk 9Ú¨ôbßæ\ÄÏ)â·‰Q¢òÈí~†(tÝ`á![GyJ¼H›Ùj5®ªm]ïæóÑD(" ™2U‡¼ÃF¸œðx¿Y3åxˆðe&Ïá-·@@¯m×ÙfÅlàñ0ßsjQÌ`€–]7R(#ÏnÇà,q‚iÌ040E;F°O‡:ù¶ µ¶›(Sl T¹ÑGè) Ë ª¨žœh¡4!…®–‘jHûvãx)ìÜekô^¯æ„ô±ÀÇM/á-Ž&û”¶b8×N—ƒb:òYp™B¬µ&©¹æAÃbè¡K®iŸËĉlãгºº_¤v3åù.q¿fâkTŽü‹ ºM-jp+ %hÈ®– €p] @ DÓlÌlmH0 ¥Õ¼Î9è ‡‘å 7M¾2 „IgJÿž›|rM¬[MGm±;CÚW9 œ ªk‰´rܱ+]6KJ!”-ù‡ÌÚAËÕ~[f¤â ³tr”Ë¡rK‹µf|>*P‡_UöêÔ¡f'·ÏAls¤ ‹¹Ù¨¬ìµ ­9~´óÀfä”+G¯°Q ›]tówþùlǰÀ9.黊¥É9uû¹ê .[‰n«é¯ø$}+ÍÞ|ÍBwÑ;¼ùn¥ßøWôñ¨lHþ⊎­>£¯;*ë]þÃ¥=V»ç®Ïyï#5ýü>½ÌÛÇ6:yÚÚÌ#WO^2¿‹á­½˜Yó 8ÑãÐäÔ×s{Ô|ÌJŒ¶á·×y̾cÖ †§=90ÔvJÏCMìÌ4ÌÉ-k&ÄŽ4hˆšÀ`ÀùP]»æýÜâ/j.Ãï»qÑ3§Bìн0%˜®S´D”¥†öγŽRñ‡· îȷàóyÖ!ª¥ˆ†¢DƸó+è5 4²Ö·+¥‚ÄV‚+zœhÛ®'Ä’íjʼnEè?^°Mø8QAŸYV ôÖÛØØnBƒ;²¶~tû(ö ñ¢¤ú“ãˆ}–>õk£t£USî[®J3÷Ïàuõ&NÏǨÓpÔgöêˆ%8$¹Ã}ÐúE3»c—–ÔÚû2—”ªM75“À!F dSF½[·1õzm @7ÔŒäÒµ§žRÖàöî!‚ Šwb[@Q‹w`Kv¶Zä›e½_²µcSŸÞÏ[²Ãhp• €qW±½§´¡œÁñàÐf«–»Zny‘ ©Vð¡´‡‡ü£ž~a„«ÚíjxÛå±Úá@WP;U§mp‡ÌZyÀ´¨ãœYÓºdzçÖKɺU¥÷§Œ‰:ÌLöÚÈ šsšH8©wÍ­DÈi»cI€%lš¸Ýhÿ#üôááa»_çkŒù„=þHþ“ nÿÓn÷÷Üûdšt ýoq¬+ümSÿIyh6ðÏ!€ë1‘Â[9\‘ ¦™ß\dfRКjZÑç+âãõÔ*îCÏÏQo;>ŠÚØ£Ò0ÍfïeÏ­®it¨y‡rÔô¨€1Îñ¢ˆ3ø•±M£s2ÞÉ`Á'˜$(—®‘‘RÛ7‹ÈT >Ö„!›9{mk“*°G¶Ö +÷ßʤ¢øEaÈI™…’·1!ù‡ÁG³šáµ1ØøpŸÚçUËkoˆyx,zà rFáþ…¤J°IQ”Q3±dΤ_búèÆc²^]è¯XQ>SXI¤»å3  b.gõB-¸÷Ð@šÑ½Y9GÑ Å¿‹À6ì!âyÚC¿ó Vž€Œ¿½²ÔLä™WvÒKâØÁÌ“sc^ˆ7L#6¢9Z4¢BøÙ€¦p¬¢?ÙÖûŠ‘ÍíOØé=„ªUeßÃÚ$ß ò)Ѩ=;L½“õ¦Q»°Ý*u^;Q!­ýn‰½_[ª¨¥Ÿ‚2IðønØÂØñ‡šfZáÀÕ´(6þäñn·cøiάç «l€Ùr*dšƒaÀ'› g[w±=¢&î´üZL‚ŽAãϕưYÍÙÚ¢ Cû@MSTãïmM&è ŽÂ)ðj¾_Ïx½ð…¿6dxÖ\‡6Ã!8°QæÁF¦Rž½èišçNŒ, šKŒ‰ï0šô³Dþ5"ú^˜ä̈y#r¦¥Q< ZQ-;3gØ@ãhgYít”O †7>âuEc þâöª«3܆SÝ7¢fàÞUò[F˜ Øc*¤+ùâCTHƯ”ø„nà H¥¢rxÍìÃñ<µB™Eøxh {Å·S{|ÜKÔX¸Ž†ž£lŒ¾™ÃÔ! s†ifúÁnŠˆ\ŠAÊœ5ÇEÁÎ’ ¬¡\9(Á€Û…=ø€¦ ñ ä¦#PÄ›ˆ4šæ“œœ°E#S *Nô6o•½L1b‡^ºÕBkm•ªÜòYþvòÉõXßN–“¾ꜰ¿.ó ´ ‡ÙäÖ”üSÔœkFÇú¼‡@Ó¬ù-l=ùúœ·£´ä@8r!†í:xuü›J½,ÖÖv=!'m÷8ÇTÓ$¶éºƒJ Á÷¤o{š½½UëÚ%4º§~ý„L}¬úôãíßNñ[íß> M¢µûw'ýxxïtŸŸåëÆïš8µ÷hÔþôè‰úP§MºÇs‰|‡ÊÌ#Ûj¤Ú~<¨s¸g’ C3ÐN{§%;´šu=“B'ɼB§!ÇÄ' ¼ŠLålŸÙýn kò¡)šm,HUœ#'“@¶ Ê Ÿ˜¬j–¶ül0/s\ÈÍÂeÒ‚6'‡xÖ)ÿÁ”¢ÜÑ¥sô>€¡ÓL3nèœQn’A–95EKSh¨E"Ÿµ& Û–tãH}¤—€ÝŠ~ž•(H îÔ¸“lY'á‡iG7G«.LcÖÇ܇›Ø <öñ*¸".YÈÁ9œ ­Uª¸" yi†¯VKPÄÄA ¹¤Ô>a·ýßн3ŸZJ’‘ŸaîфƩ‘Eb@èk¨Wp a ,GOËŽì$Ú©JPÄb!:yL>h5-xxÊÙª‘àbM $ š Š–ò¡î*p…lI…ŽöÞ`È/å;:u->¢ôÀ“ñ½Íöô1âùb¤l|½Ä+ÔT¾JÓ~±á7‹ö“øËD·qBÃ@lŸ¢ö¬™*áìÄ ùUè±óà¤Ç¶‚8Âëªëbª†ÝM$Þ´•Q‡ #Þs S€F;vôŒb½7!v³öG{½’·ÇôUhŦ¿¥i·Æ°Å@¦ÐS®#áMf ñCQ-¡[šZvBç ÙŒŠ¾¬¥¬:lqT!ÇN0:™×j÷3Ãy=|ZÅû¢#7E/M‹‡FÈÅWïç#¨ÖY7Ø óäþò†Ëùs96ûÍj¯€Ž@‚VX.ji ‘úŠÓ}!®°!KÍœÃlg:?µL90ó\5€¬"ßN~ >O“G·ÏMéjï]ƒ¦Ð¶Éþ0KsÇÓÉ+e{PIãÁ·ã[TéÒÌ<. u’W'N€œCžG¯NRô‡^!äôö? Ðïçðá|Hÿ°ú' þpÒœO˧|ðö| ]}fûEÚïcÕÍÃÓwõ|D¯ö±Áçjî ðãvl0~•2|ìç)%µhì@‰+T-O/GD± Î0É™AnI€zS`Ïû‰ñ¯#ABÀ#GªÙ¢<œx†«™iÈ+úCƒ¢³ ìtíÚ íý|`ÍÜÉÆt¹¨®§ƒ'øhÏn…|°‡G­Ñ)äßÀ%®à@y™Š`õ¬•<°÷¨GÁI×¶/Ë^#»‰Sƒ‡Ê;q:Çë´£ÈZ^*qÑžm RGØ=Ç“þEmœ+ˆ>`âÞj®gƒ*Å‚F®3 ,Šw>”H•öÞX¡s•xmlåÌŠcΡR2–sqÓ>³QŒ=ÐŽÐ@¦ݰ2åLÕÜ~¡¹z†á³u(–ºÎm0 gŽ”¾Àf•ü9ÛBy²Ï^oŽ¢K%v®l&göê™”GAy.’ïuèhC· ý<ÁÿÃ0ϸz©cl‡ìDõê>s*rWØÛ8¹%3ÅÅ”^ øÙ-Ëäí q4ýê ï6‰h¡ü™\뱡YBSßêõ×¾Ú*uÿ?– áÎC:¢¤@ý-ŒÂ¬9kµšQøP våvàQDŽ9ÿ uԌٙ'–…<ëxýÁ…d¹2sèZ/íZ‹°ΡwgÆ^558 ú9Ï2v‰/ƒ0']ËyÒ4% [ƒ=ZŒ¼KСxõΠ5Ð2ø„að’ë`0;W¹Ö˜r|œ‹`Tùé@2ttúK7Õ7£o@/’Ì-ö÷”-[fnC›×åùt€têÍ.Œ\4¤ß\Qr|acǶ¦Aß%;ÄöjÙ’QÒy˜‡.—­þnt ‰£P£%=# @lšCg‰ 'óÌ©ÊXâÒ ²„@[ÿÍø#“‡hšCfÊ "]tmm_®âh„"œHá?ZjNYQ¦¹ˆ4D”coŒ¤…!NÉ„:›Å 2Œ²h·5ö`f ‹ NØy>6$¹ndîe0<`œÖ-$Ðêè‡aÊÁEæ-Q'9p‡·*Í@¸.»èöa}í´3çâré€sPpÔw²¨ƒþ—òMÙ&N‘ê¬ÓŠ:ò‰åÎ8UºWJø5ÉÊ™uœu•’?¡;àyTµaÿét#¢>Øô9¤¿q¯©¿­ø‡Às³¯ˆå7n¢hÿ¶'ÿÌýÖ>ÒKß7õUû÷§¿w‚}Zÿá<îì'ùx—I â«ÐD¿Jò|dª7 lPºV´>!Šj¥ÓôU+?-Þj%¿mè«9ðå,¥\ÙÆX-†ghöç Î8 ! :t'Ì@›Øi²1æ Ÿ((…NráéG[Ër"š—J~³áoŒ Iœ´ÖBö±Ooѧöçw鮜*Ü!c[Ñ èÅ jÊ¥RþC*eMý+” Ws’c}=ñ¤q:ØÃÕPjŠ;¼«—®ùä²J3|>ÅIò°=‚A§ì\š9²>JŹ UÐxû~9‚+¨4•*y X(ŽLx¦Ì¹‚”àwa ¬RB -¬4FŽÇvAðày6YkÈ˯ëqdŽþÚ‰Ê0ºæ’Äí½Äš.GÎçs×Ú›{â {HÔÔ_Û#¶¡ìɇlqhL•\ xrçÕØ½_Óó¿Q:ÁÆ&Xœ’@ùL1N5‡Ã©ìÀ’ŸaGÇB˜€C¯ÎŽž:i+jœZÏ23X¾!9ö™í#(ÚhRÏÚÅó‹Ã„ˆß”‰º±»gb$Ó¼¸ÁNYR;v|`éd*enøVXø—º¥¸-^†+·ˆ7Bú+ÄYŠÌ÷rûäª¦äØ­þf>ŠÀ£3ÆÊ„fûìÂ"@NU¢µ­›ñ(3Ì–ù·±MuOé+ ù§$Ø,Îm±OÞüÏ®Þmvi•+ÓzK€“‹6jÐjQV«%uºrl6òxà)XwW‚QÐϹž ëÛáƒmŸŽ-h?šóöf…NâêÀN[ž"~ë“ÚdíßèçUì5Ú‹+€Xy¾Û 3pvœ_átüy vy -Š=î?´r«L…¢‚÷†Æ‚ÖøÂ6Ž‹'¬í_Æ•Rr]n).¨Ã›Ó±½œ×Ëé$K[ãbQŒ+vݸª±cg·E·3ß|#®‚‡ìu8[à%ƒÌÿþ JT‰g§_Ófi›Jco©óœu!„› BBŒµ5ð »\åØiw„¡Èò‡#‹áëŠÑiåµbZ(ÆöŽ=`kµhxÙP³kkA X,Øh˜r½­A2˜ ž9Y‹”®lÉ4ƒÂF‰Œ È"tòñ¥Å5w¿œSDÇNeÁŽÖ ~¯¸Ò–9–ÈDfQšj¼ §‰¥>¼i®±ø+zïq¥59Jq7 ­FÖäø’c¦(ÎU9È÷° ÌnÔñnƱ§–GOÎ!’€:ǿ՛ÇÀsꎿ„¼¤òQ?U÷iû7¼ôïAÔWG=œÅ}{ØÓßß»ºOZŽÕŽ_«wí þö»Ûã;}F>ÇëØC“4Îiì¹ã§ŽË`D@ã_˜uÓ*M%W23[ä¥iÂÞØgâ,N‘XË[«(U.J!á3&¥Áu{0È¥q‡¡zb„‹w(JHAv'í¹£¢?§)ä~i,„ù%¥ÔA AY†dW+×z.[ɳKä&ÚØ@Ûkˆ¢#ùZ]¼‡Ó%h¸íR†ÙF„c3Ê&¤çDl×ÎO@)£¢Qö&!Ó<&¶èÇïªÐ E¥,k[¦À9Èøµ ]ýÊ8ÝÊ{³‹Î" ùV“f ʽÒ~MͰf_Úä´Ánál1¾Þ~Ä. ‹üö»9ÉÍîÊÅÍ6Un…f"oƒ7^dgï=ªÙÆ*ƒfú—Žw’ðU$"Yð„n,¸È?|  ÞÆB•ZPḭ̀±Ñ~–,“†³¥eµôcÚ*EÜy­.t5låfQÀ¨bÆÛÊy&;Â’•ˆÝØÓU²ÅmÁ&±ÇÖ0ç‹=êì¥ú«*ÇÔÝÓ¨ÈeŽí­-òçð/J]–hÏŽ#II"qpg+ •qJyŠVÚÎ Z9ãe ª! ð÷¤|†R½’ûe¯thö¡©ÙwìÇGìl<Ø?B$´CÇŽ&⯓v <¸rÝäÊ ÞàDhþõ~¡×eŸrœLxÐrHòÏx€D ¹ñaíöa›²×†ƒ€vò‚Ÿ9¸–ÿô€J K[9©è:€$¬œ«°cm¨y <üc`‚¢Õ*?\ò.újŠvÂah£#lï˜N)×VÆ*lœ”‚L×™DîfµBèX™äsh4ˆ29ˆ y 'SÑyÉÆŠ%0G%ýHaù‘œ¨Aª~å0Ùn³rt Ò;D¬@;²dÛ_—Îrûýtøu>bþb›TsÕèÛK×ô˜GœƒýØ>4®ÃTyDy§³ÈÑD™oÔ¡#kŸ)‰ˆWMN‹ŽõÌ9~µm˜„•ruBNòGÎé uz´s %èhAÎGÎémDè׿û÷„½£"ô¾º?ö«íÛY:B¿…Gn Çv=¦z8˧õ?žñ«þô©æ‘îçÃêîNô箳ûHS¿'@Ï©;Þõ¹¬"•Ç^ o”Wj‚$oÈ'Á`±®÷ß³F=^`ŽˆóÁy8ž2 t¡FžŽ/ØA&O h¡`$ã3íAR0%³ –‹XÁ#Φ<o.§¶Q6Ž=9é2gAT'“¤¢]P¸{…”µyÆÀƒ¿VÜŽ‰¸‚¬h£GHEnt3ÞàØ‹ØŒfHžZ ÝÕj 1Ë–ÄTÉ·c²-ÄÏM#¬ø"eM$‘kGÃRÊaˆþüÍšÀ³gÚ‚âÚÁúç6Ô/Ê€۸߅Œw™ð{9‹VF/ÛŒ;ùÿKõ§NÂÜóVJ­¯ø§O8ûºòF˜áRÈ7(Zp›˜$‹]ZøÝ7ÇqÐB`Fþ™µ²,V΃•éíÈÊíÛ¦5î2…_ƒCÌû“'G=1ölµÐ1ÿö|D[§Âu„‚¹qÖ†Ìw5 1”•=cæ.{¤ˆÑ·êc÷V` G»Ùì U©LQÀ´¼ÕÎŒZC×þîå¦@ýJq"9(v¥œ!ã9£&Í Ê7b€]ÚÃïw™6p¯r;aTmg²´èŒÚ}n\!¡°ó|î0öMhÄï‰Ç¾+ðõ¾åL*­Ûä¢` ùB{Šs ³åÙ¥Ù4L ð”:…ŠpºV7ÍxcGÀ£ôÔQƒ¥Ñ]W·×XØ'€q#Á«³Û¦¿¬£;é+rMßœÞPôðì5wµ2ÈÕ4O  â’Ü£ƒìqî¶ …@…絕º_‘`+û‹jÙ)éÚå&¢dè”àds:¶§£a©”äiíì­4œqFFÐHSû÷ h´2á+ @TC4`ÉóN9˜ƒ"¼"¦O©.%ÓèB÷ åPÿÈ'EFÅE]OG´ÿëJ'ÿ^:4hǨi‡0þឣÒZcÉÇÀ£:€ºèOÇ3lê+¸’+©pà ŠòªDO:öª1•–«~ ]Õ7Ô9„e(ùŒj”~s¼)¾ÛÆß”N%nŸ{Û_Ö?þ£Òý³c»¿k$–s÷i…®;þÞÿã+ú‡åºý¿»ÿ7ŸKHñ’ŸP>^Ê4 Fð©¶(|‚cö]†].'0 slœ$Cǯ˜‡®•reþ• Ô–ô_Ëx±l´Îò(°äD#- XœˆçxÔ¹5,êà#£êDÇVÑjskU…Ÿ¢¢`‘væ[as‰r@&ƒ %ÛJ4…€Ž²U‰sèGWÒû¹“Q¶‘$ôbr15ÑýÝ!'Ž ÄåH:ãt ´ƒŠê6Ý™²°K‚ÚBÓÜ Öq[éÍ ï¶[Ú¶tøϼÅ2[£MMé-oRªÐ~05;/s2*4YA—ó7hT*XèÛÔaç†Ìa«ÅÅ6iƒO„§Ø!q"8ó5“4\¦u¿ÝmC°C+¥=xOL ÉÝš+Xï8ÒêÔz±ÙÞãw2-³à gÖyUíêšß–¥wî’KGw©°gÀîëGà.Q"]LÂN¾)YÞdrK‰‘BmŽýƵ æAƒ%þ—ï¡‹÷ o·rPP_g$™­q‚D´ç×ßLû÷ôé-À_¼ÂV•¡™N—•¸‡Uî³³¨{®]~—š¦ÒÞ|K‘é’[ƒ€¡UÑC 8òCéŽÕŠ®áeÂáŒ%mÝì^7šÈ|¢w&«ËÜëo#çHóÍ}BÓ‹1ÏÅ·+íwÌÎ!„ð˜“y…ÞŠ‚"hUÁ@ت’‡° Ypºêê´L¥ž‡õ]Cb¤„§ÇZZïW±mųЭQHÔ´I@áÅsÌâÅäƒ:òùäV¨ŒC,™e„¶ª’®†(ŸÝ…£]ÊæêBÆ^ÏÊ»ÊTs_~õ?žïÊá Ũ·ßeÕuPhãJëR§³îØ¥bu É–íÔ•MÔ8¸:~Eð“Jº-Üôç•GÚ9ö‚S·íí$€áÆYE{n;oåÔ+•½¿º«œªÅrþ¤Á~g~[î<~~ÆÓG$ëî¡‚Ï']BûoÏþï—~7þéQýjÿWu~ßÂñ”}*¢åVÌÓzÍíFÉðGô½käÖÑ%"7KãðF3xù¶ð(¼‰Æjh³¦CÜâP8ö\„ò¥7Ç$XöžAYþQYƒ±;Ù„Üg!0ˆ&Ú/hó ·m {G¹§ä­e¾íFgîAÇDƒTU¤I%\|ÿz?_`¤·N­R ±*äiœ‡):ª-OlmiDgNæôpvP§,甜°WËŽ%›²TM‘Tÿòÿ[j6ý÷oeÝg2Ûqi¥!‡ d²´ÿ®ÎºÜ1e8ÔÑ–÷Ii5‹Ž,f;±(å¤x °á j2PÉu\ Ö_‹²RwÆ›¤‹ò-O¦ùXø×UÈó6[îM;u$·~‰È‘¥HD;]ôà.îa8¬á/#F:väG22%z ‚×¶ Ó£Õì=o1Oá»ôrì²7É¢Ú ÎW(¨paæ7;=&`F!¿ŒqYæ¡ Â?Uã~íß)•ãÖñQx\‡® >‹ÐHT9 ©ÐG÷mÏlbÛD0êk=x1@é)½dÂ÷çÈðÁÛÃÿ*yîvꡤ80Uš¢ýn'+·Îªì0¡ßco˜ÑZ¸Ì5Å›nýE'³"fïôdÒ–-·t{+t'´”áÇ’*5[jïžn‡“o\ÙÝóQF ï<ê¤öfÇä:Cˆ8¢²øNæ¾LŽ#Ѿö‡kø4N‰<oyaÍN’?ÈÓ­ ZmßÃèiã;YD°«ÙX³œñýäv¨œÛ…Í´Qº¨~LíGœr’œC+òd[ò®~¢cGÞž÷S,AÓ¼…ô¾Dà ñ/«)ÓîšÚ3»•üsWÁÞû™v,Ã=à‚ºž —©M&=¿áõ9½½„À9AÙu݃ö—®?w`s'çGºg¡O¤BwhôÐÚ}µOZøMy@ß4øá£/ùrÎçÓÝÇøí¹wÔ­ó÷W}î×?ßïüXíCy ¯ôñŸÒÃéþ¸ºaÏ!Næ:˜«¥…%(4lC¶¼BEG…€é¹td˜ÓÁ•{Ó8Žó /.&€úd$hŸ>ðB8ž $ÖÎi„¢x ˜Ž0Ö~i„{õvµ:Ššcl™IÅL›ÙÙÉÌàû±±ppdܨC9RÚxj›hŸøÔµòñêtç®;˜—(ˆpì‰íW®o§«m7hh-‹A‡ž['Õ”J¥&²à Ã_®o9@¼.zm]¶“n¢¶¡ÆÛí <Ð}ü¨¯¤[m¿ûXOAw!!;Ê@8i¬Ny•e~ÒÓå'Ë‘{.ü=Œ›mhIŽ¿ÝöRÒ[ í…å¿=E·Š]D„«ì¶’ß)FáË ª”'­ïàçq^b s¸ˆg„R}° +Ï.,ª˜4¢ÔÂ?¤âdÚ”˜ZÝA»‰É.UÐŒyì/¿ð,'w8ßBî?âAPÃòÒ*G‚¨Vùmî‚Êx0(¥@Kù…XjÜ–Ún"ØnN!ù…¼uä8ÚËóÃöCkRëUÎnb6<7Œ‚$^’¦Ç66|Š’ažÚ€랧ƒ0A7*¯õõfâ`T>É* \Î_¼Ä |ÊRëØFh(þ…õÔÔcF¿žOž£;½áUÂZ @Æj‰iÇDg*ÿ($ÆcíKc¼ß®Q\™6rÝ[ßá"…éO¾•.?%4&ëˆ% oñìÉMädo›¦\ßé¢~ç%«è×"çPH%²xCNBÜ(S¶ž8éÊr pÒÉÿ£ÁÒ(ÉG‚–ü$¾óÚ&ßJÃMáŒÆïJÿõ~Ñß³#Y ~~]‚ÜëñO  %!~ço÷X>£(ŒÕ(DEn‘Ø,,OêÈl¸„Cä­‹\&ššpÐÕËIõ¹œ‰p$ë3·FTÖwŠû”ô{ïãÇÒ}¶ç7‡t÷±ûM㑈NPîºqú›«øM Ú>ý‡ÿåÏïóÇÔ}qùÝÜ¢•?ùøÀ?Ý©çó9øI2Í¢ ª­¶¾0 Ì!6¸˜ÿùºì!!0p¬ÎŒÂ’ŽvÓÞ$oéá–)L<¶ z9 Uµã¢zfH]vVTRa(I Gñ…ý±TiÍ<6ºc“„ÉIþ䎮§ÔýA~]ËQ\W½_ñ¢J9ó½r£2›`¨¶îC¼4² p(¿Vcu'E–òh-â+Êm§n(öa@~S—·miذÑè’eþÛùŸìA&£C€RÂ'*­uÄlE÷ÂÞ’ßkbeä‘duŒuÉîN‹º;×"=‘¤D\Ò¶kõ!Ý…ûì×Ì¿‰ãFâ¹îþV‘IüCULJÔ¤:ûÚɰƦzU›aÜÛÒJÜašä…¸?oP”!¥M\;oV)Ë,H’e_áÚŸc'ý<âœÜ•M;x n ;ô©¶þ‰CUðm©H&e8*±SeLð ul„.JWJ$p4Ê0Vú¼…/¿¶·T=ÒTF3 ·ïKÀ_ÃÚñ„_(lîQ³V@éáÊ>b/ð‡A±²s"ØBÑÞ€')/Š?룚r)µÖÇKm¼ÁñEq“±G—ïNÖúèÅii\ÜJl¡JÉŠ‡½J„ãqÁH¶)zeº?>cz4øÊí(% UVv‘üS;ö”ÂÇöíDÒ_ ™’°©÷í ‹…ö¿ˆüë –Wu ¢ÖRNчÝD*õIÌE¶¤!]‡2 ‚è×%¢Ž‹€Ç¢›±­EÈ÷0×°%´\ºc 3qTþº\/'•ª"O*8ü<®`ã${Ç fãÇÖÐEŠãNÙq´[)Æ5<%yx8‰3 |ÉJ2Üj³Ú ,Ü£Ë?‡m£ J$Qk+På'Ôê§´ êáQÁÐŒúG Ém{‚þ*r)»ºwí7´°šƒîä5…%E&ìql"^‹Ñ‘;m›+­¢ó[øèˬÊÄ6fT|uRê~ãü{æ}«#ÑÅŸ-–o!éî¶îm´¯ëÏ1Zô‰mò0·Pö»ÓxZf!l×EÒšÚl–—QÝû'xúç­í4ˆ>® ìêࢱ&­ª¸§ <Ù ¹¨+=)lë °3´þVz´B‘ÂMUÔ µ¼Þ(•ê€:Ý)“•v«õðóÐî°cÓÖÑvµ¢ zè²hªÈÉuûp‘`Ôœ²@D —Ë™•9”»qdŠ8¤³”Úßà˜:ÞüµÞÒ@£ué@7ä@Ûô=-ЧWá¢Â‹]?#0ÃS5? TM㎅(uÄ•:[w¦Õ¨i=zewlû»Ýšè’ùà[aæñÕ$dÉG?XEaŒ`øè(à n¹äÒÑÇ%—l°à°óU‰Gsò´Nw‰Î4K›ª(¢ß»±¬Ck×óéæÞöX,³Ä¤eôF²1ð0Ã9m‡ÒES÷h³.¤ÜÂÃaz"*kéÔRMbá{ëàf!rŽD=ÿJ&‡$d•óùßõT\¾(Ÿ}{¹üqSçtȹøíãC…ªùГËïÑë+ZûP“§ žU8¢Óv‚Ÿ‹{{þü’¿ºÿ¿¢¦ËÜŸ~´KÇ^òX²ƒ,ÊèK¡/ñ˜^Z;I0¬<Üô‡hì)еhûбHÇž+ï|çbá 1UËZ‰:CHòö¼_Ï9¢JM _ÐS¬º ±ctŒálžÑ6fà"ÅÓO"+|{Šã.˜„òÇvÊî¦kK!wÒ˜¤sùtm¼§Î*UÀSXŠÚ±Ã; €‹Ua™PGŸ*¡îd¹4]i=˜Ä{„Šà.„?@„målûù›è%HF®u=:ƒ§„Ì!´c¹tÖ«N"¨îXK_—Z¨ãVÚm‚:UZ8:sê”ÑÑZ‰4¾¥A;Ü3O]‡$´ñà QƒÆW~.ÙK†¹;o•gÆëÛûî¤õf£$¾EbŸ(<lÔÔÅu ŸÊš‘îD¥FÏŠÏNÐçfwœ‚D uqãÃý¯Šá-®m¶éÞª¸eìÓ©ÐÕÆ/@ÐøQ•{»ò£·+2þ¨¢ŸE“ˆBï1@êþ$·KÞWLõáÕ¢¨ô·ë¸Ô¦|2GßZÀ¹ƒ*عÿ¤óÙ>Ÿ¨ý]Qå »†BSé×1¾Â ¨Ýþ!]¦Ÿ^$~Å7§ ׫Æ}xÿ>Äo›ÅG|kY âÊhµÈ¤“?%QYŸ*)è£íA’yé ró·}³BwPÏÃÓr†ÈšÐ´WË›¸Åö’yU¹&@ìüc$[X1tC±?B‹ƒüEr§x–dfúÛ/7¢Óæ#óÜJªæ¤N cßÎíþKù¢Ü=ׄĸH¢úVXBZƒP Ÿ)uù—Q˜£…go¦ÕßÓúÇ=½rV¹|V~óÕ¿]úè·/,ùxöSïÛÓgû?ö¹wÈùô°ó¿£œ¾º®ˆL_ùïÚ?ýqµß×|¤µ¯¾úÌ“fŸa„jŽaýjpØRJP$H­àµ äp´+òhZ8ßcÃ1:re˜p0Ú€ÈÇèÀ­{§³¨‹Ò HŒdw[ÔØÏyóhèaœž¯t¡4IéËwÚzÈt1´m«­#ö¸»h yè¡g– $+ÆÎÈKº;j@b¾.2V#ÿ ½7†uè^’WŽMóÂ%Ü‚ª•r,×$Ei•gýµdP—µÿIÉm¨¿n¡‰Q-ë½³àík§ EO~“ÔAz ~”7vUB`r x‚yk›KSZöç[§Ð•çýYÙüR§DÙhßÝŒâß¼‰Í»Gï'ÛäÄèý\ ÒìeG·}ËÝ^Cc/<êÍØÔ š;Û?¡£ Jî <2òF,˜™Ò³R}ÃýìáýЗ®œ ~04øJùjéæ6üJ^«xþsËBšVz¼>OKØß EìžÈ!ÝÚ·í)„­ÝC¿Þ'Ývï0pœ©m‹š£ÍƳ¥«ó@°,¸2NÔþigÿ!žå>EÈ‘Zž-³šãbgÊaÌÅ2„ó~ ¨Óm%àa5cÏ9Š»þö5 Ž„$/ÍWq°cÇ2Á2M<3çøWò“@b¤‘mÞÙðT9Sº r\Í:®—.ûàŽøo†“R.¿ýø¡\/ÿ鯗ßåïÛ¿üiWÿß+÷n¢”œŠ·ˆj”Eg±KÝ#fƒ _Ý“sÒÒŸ<<ƒ^2'½ä>m×ódJ"%¹¤È/[èd,P,`Pc,_-$´-^Rñy¥Í;‹{0ÜI‚–I†ävN0úá ´ùNéqñˆFÏð*Zj´Ö§8ép.Ý@믘ºÐÄXá±;F›ZÊâá_ãݲèƒfÔI­\$Ãïô‚ ¯7>²¾±œL澺IIßž%÷ <‚!ï§“¶]ÇÇân¤{ÆS|Ø#`¾©6uŒ­9m>j½°>|¬Ù“óiñÛ¦z;ÃÎ;M!†¨:£…ÎÛp=v,b|kÚIz-Lƒk׋"Z;XôD‚bÅ:40‡+x¿¹‡ƒÜšMÒÈÖÚTûâÊÁ‡ë8ð.uRÖ:E|%bè„g8F‚rñuùþ$Bð†ÕôIjźÃÇÊרľ0Bß1¬¢XPíÜ86<ãA¡•Cë ÑÆÉdƒjJ®ˆ÷ùè[ím‘…€‰"sQ+ ;ùcXD®÷Fy>i€ݽøÚ„¾Q÷߯ ï9†ÕZÜÙð&įzÛ-KÜ“»>ÜŸî¶‘ÆT|u}‡ éÕ_™³-cŠLE3ð˜Dœï¶e©oKâåx‡'baœÇ^/^W‰ÎgYŠalÞM2‰s´Górû{½üŠuá\o„㦠‘ .6mÐù¡5 ],9mýÙuðæŠ¿ä„¢³Ÿu‹ÿ\î_ø}mÞÿw©ô¾Åûíry(é_¿BÿcüÖûû{*_¾*¡C,Ÿõ°·§ßÏ/®æœúÙ¿Šëõ¶çz»9©Âùöíý!÷íœ/á\|u½Ü_åíIº÷x½Ÿüûô‰YÿþŠþ ¤÷-Mìêâ¢F]¯hž ‡ù;O‘^ã“]%ò¥¿¥a ÁŒÄ;$ä1±GdªàÏŒ†XMèâô®ÑçâáÜ€4¾¢êOóã®:Ap®ž½G×T Á«Ü¹²‚w<]êp2a¬n\ÓW}ŽPwUp®/$4ZH¶Íçÿ¼ÿ¦ai«†mKï6ž=AË1.·uqP2jÔác¼œSä´$®mÕ¾ëÖ$ £°Ýéhí/ÁWäo•GR >»Ô ¹«ô ·å,Å«ký‚õ4T{·¨xPa‡”oßWRbž€m´å“dŽK1°D|j?(¸æpûðÆ ÓuºÍühpÑW±fÛÆša´cèXÐ’‡¤Mª|<$5WW»-!«vSk)¼Òõ÷M>¦rho¿Šòp>k&oRñ&7ˆڕ¯z­t¡à%;{°9¯mÓå`—¾:Þ®Ú¾‹”(?dèI|WyçÝ\ãc´³bké,Ñ#nÎ}Žéþ›¯ngI!*K¼0¦ÒPºk¿}h<=Ýσßù!åØ%:(ú’SâÙ‚Âwé"ÒýšúhF=„Œï:Üãe‰ý0æ"z!ä\£q ç?±ô·/“Âö%m®’y›†mÿþôæ#´\Ï7†1ÿôiÇ%|{¹ûJjèÜž\ïÜg¥¿)—?®ùßRþ¯èÿfyx ¿y(ÿN¹ü¦rý JÝÁO"Ÿ»I^½Ÿ±àÐxGƒçsH‚‡V¦SÍ1ÈÏ›;7ú[®Zœuh_Sß%~o„Ѭ$É[³¨`£õ­$u }¾ÑÜ81¡è¤ÀÂÁçÂ~ëzÎi“¤ïwþJ“êê6ƒ8²¾8À뼘ÖAaôþYUöKСQ{:&bmçîöh¢Ï+μ‹ñ.Ë­ó”o—Kôé%HšÈ(i4 š">²$êé픑zH€tŒx¦« ZÿÆ¥Ã}ÚI†pŸgRh&™ÏýeÆQ`ÓZùfæûª{?¿r[¬5 E—î~—çh[»;†j×z™'éøVtÖô]¯ x§ »£¥· ÖMËÝá.èb¶·©”=ð¹\3Ã;Tˆ.Ó€4§ˆ»§ÓmV‹ÏU3?ÚÇ›êÒ•¦O]Ø“®Å]8ú(öÿÔ¹aö0vû!·0]]ÿÔºI·5CRùœº…ÆO·í.íüìÛS|ÝãRfòiûá}>]- ºÐw/ß¶Û‰îŸK œ˜á`óGð'"“+ÉqrË,Òê¡Ñ cS×h˜Hâ]Š[ì5­·´ûÙßèn}zXÒê† !úò<éËÛô>öw¦ŠéëÓí_R‘é¼~—º»nˆß?JßÛÀNA㜺‡Y–·í.nwQ¡ß”Q°‘l8õ9\Ã)>ñt9לŒ%e0ðét aàûÎûE}׫iÙâûлœS|ÁÂWáªï¦W„átñǛӯÖ{ÓŽ;ÏiøÜîU¬d]ÿßz«¹ÏéÙ~ƒÑænŠ(a|+dOuFÉ¥@<évyüÞÌÕ ÃÈ•ìº2QGE ^K¤ýx·]ùnûš¨‰;-¶ûa¦ô®^Ò <ßÄ QzJ$Lãë'À£÷xC¼ñŸªEœ(–p_z_ýmyïÒÿêz¿ñ› ;ÿÿ½ÿñÎϾÿì«p£j¾ßí¿Þïÿ¸óqã†fÞÿ§Z~¤ àâKþüÞöžYq‰rñKîó=ÞX_*¨ñ{X:ÿêe¼[n| ®Ç”dlƒç{$³´ Îa ö_îZ]¬çd1ýzÜ`A•ôrìÆý莇?èš$œ/Q‹œnŠò¦@ûš5éÏdÑ‡éŠ Á³dÖå»”„Oò Ÿ“XŽú7é¾ÈiÖ2±BÈ“ÈÙÉ_%š:Ýnʇ§tÞX«K"1µŽd{³û×õþãz›=ÞþdZâºqE:ãÝúWý=§‡ýeB™%=õØã?ëÐsï€xºs_½Þ/mÑ9?L0¹Þ¦E„ó]zm;8~ß~êÆùÒÿêô¨‚“åâv¼u¾ÑTh䈤wí±µsú¨-½ר ãx¹†>Ü6î’=åsb?=f¥úß%þ½í¹ÜöÜݨþSnR+š!ñðËù6^RéË´ÔÈùÖB¯Üžwz}ÓWVâ÷ÿðèÓ…‡7áÓÒ·1Aâýšü$—›ŒêñÌ f„.¾ÛÚóÞû{}Ol7Ò!ï½ .×xë’Ú¿ÒáÜùaúrøžd¢½Æ¿×;BëÏR{ÿßò¿å¿¿ü9™´-?Î)+)É+Ö`¸yP¿G˜¹„”›ÛÀ¹>DfoÂçØþ1)Œ¾÷÷$n—Й`hè½÷FkÉ.=¤ ð¤±ÿ+.þ ¬õ´w´­‚,z@ ™[šÖ9‰¯ä( öAQÞ”i_¹|¢gO‚7 Õ£<÷“ë'»¾?µð|N¦÷)õ°oŸn¹™¥çä±Ñ“½†;ü ó/7ißÓh§~êÚ­º{—;…rº|¸´ÛÇ/â­wÛ};ýüÙ´Žó‡tµ¯¾ê)ܨ=ï?öïÖÔùüEŠÝ­•èš»³ ®ýܼó½v¾Üiü¨Í/ñÛKºùý=½»nœÏ÷¹绎ÅKRúi¼?ü³t8ß;"ÞãKÂMŒ<¦J¦nœR53!ïÒ6¯Ÿ&.ž?d9öò3ïr,Žë?¾O“-oàt9Ÿ¿:Ñåñ7°¹iù›¸¸özuŽÒ7žýÞËᣒÿ$ñI„™T®ï×Ûá_ýí›Ñrã Eÿÿ›k`§q€{‘pÓÔ}èì9ã!„‘vÒÝÑòëð{ó@É}Úá³0‡Ve£û™,Öœ]l¯næu¢÷ï1ž"ß%Gav¾:Ò–"‰ÛyìÐ$Ý(‡Ô gG~Tòy=E1/1bD÷cËFó®¶eIPN±GwÎÓ-´ÇÕï嶘ñ*}€e8]ìÁJkȈ‰¤g”z6_[‰{|hRCddeØÓ>63sÃŒúaµ–÷s ú¬àŸIüJ:½ø+¥mm,Ë»×õßMlY{?¬£[ŸÏÇòú@8œ*ŸáGšgGé/öž%Îut1;J{ÏT]Õ²–”4Z­gé³\tîr‚òŸŠgZÍ…Y»J‹¶Zî‘7†õøŠ#VX> >> endobj 10 0 obj << /D [6 0 R /XYZ 36 597.056 null] >> endobj 11 0 obj << /D [6 0 R /XYZ 36 583.1083 null] >> endobj 8 0 obj << /Font << /F19 14 0 R /F20 17 0 R /F22 20 0 R >> /XObject << /Im1 7 0 R >> /ProcSet [ /PDF /Text /ImageC ] >> endobj 27 0 obj << /Length 601 /Filter /FlateDecode >> stream xÚ¥TKOÛ@¾ûWÌÑ–êÍÎìûتÔF-Ä!7D%¤¡¨ÿ¾³k;1IªDB–µ³ó};ï]É‚²`‚ÒX¸_dR´¿…Õ †ÛçvgÅ5Ò9°ʈ”¨DÐÎn< TÆC)E–âÉçìæV”]\fI«à•7Rèà'yÝk³«ìûÆ"G¡­P–ñÿÚjÏIXôb{Bí˜*º.:£ŒpZ¥Ì>UÙè  À*vSýE^HÎŒA¶(=TÓ›|üT°‰¼^MÖE‰ù¼À|Ù·ÕevVmœh§„·†Þ5+œ"êjªÞ_ÓrÛ&4,8<4Ê "6\’RÂúà†¤]rÜm×Î<³[Pûå:š ÒÎ"õÚÑÅ‚à󒃄AœÚ ôx‘îPÜ4”޽Zjsp–­éHd­°»Ó·‡œg‚Ф¶GT;Zì:ŠÝ{)¤r†Öö|ö¤c>ƒ|ë®z-ÈçKž4¥òy3­yèXQ7S·:Î]ÃÂ:â:xaùîw»™òUÝnV ¼4MœÑf¶›¿d´Ð6¨C=ÝÍ…¯ iNÇsü%ˆKÿ't»¼%‡P¢æÉo ƒ"CéZè ±._'±.1­_m†UáSÎ>ÿû¸L؇¶hÌ¹ŽŠqA.ÿ¸U6ëT¿hcùÔ‘Ï®Æt¾qpY,Ì›Y†vù‘åŸz×;nŒË;“ &)Ã^­µã+È…d{Ú[ª‚ƺÐ=ЧÕÕñ­•ÖѱênxÃÓh/|gìé‘÷ƒóÕ|VÀendstream endobj 26 0 obj << /Type /Page /Contents 27 0 R /Resources 25 0 R /MediaBox [0 0 792 612] /Parent 22 0 R /Annots [ 29 0 R ] >> endobj 24 0 obj << /Type /XObject /Subtype /Image /Width 1593 /Height 612 /BitsPerComponent 8 /ColorSpace [/Indexed /DeviceRGB 255 30 0 R] /Length 24021 /Filter /FlateDecode >> stream xÚì XSgºï×2áî ðVu4¡£œ´¶öâ(ŠÖÚž (¢{ŸÑ:âP:Á@@¥­·j9>Ó=^G­µÖbÛyž3âVÁT´Ön¬ŽX;Öjw= XPÈZéYß·VB‰r š„ÿO!·•ïZÿ¼×çî÷ˆ»m7ßß²ûêO÷Í|mÃî-<<Ì‘ÿ¼©Åös¥[÷MØÅàUÜ›b9å‹gø&éVÝ 3½¼v‰n•~ÉÄ›ªÒRg¥¥¥¦¥§ÍKM·ç»ï¶Ç_!â ly)á'*ïmû™¾Þµ9ßS¥¹6û.ö1x•»qe‹xMÐz¹v›(»‹ë9s£)-™çÒÓ¦L™’®R‰Q)ÔZ*!V“¯ŽW«Õq©qêô¸ôôk¿¦6räåM-ü•yö2x÷Å‹ª¹T0L57¤û9ŽÛ¾=N§SÅĨTj•"&:|´Bót¸"mÄ=îjÕÜøôyiªhE´jS‚F¡REFt:¥N¡Ó©FÇþ6X¬È+ˆŠŠŒ¾FÔ#68rT¸"R5(A•šž¤Ú[$ùMÔ߀Ï^„—"Ij›ê¦O‹SǨT kÑZ]LÜo§ ÞÅÀxÍSÓ­­ÚBíZáK»AÞ'X¡ˆümQNÔëºudä(ÅUL¼ZŸ—¦Šoä9ŽãZç;xºlp¶úaZ›®ŽQGk6mÒh¢µš)q#tïÇßÜÝÄñ&³ÙD68S=Q˜ï” :…b”N% ÒÅÿV§ÐhF©UªøÔék%å¸T‰,E¾¾©»<›­…ˆpM%ô”ίþ•kÚsë^\ê ¸µJ£ÑèrbǤŽ}Ó´i+Ïß!¦?ê¦ÇÒËÉßKÔxï†262V©ŒÒ)1ª…F¡Š^"-.éW¾ÑÒ¿ñbUàáÔIgr“XQUWgºY7=nÌhÕhuB‚zÔÚ5›¢u[îÙæ&,Ñ&®Å܃[QQ‘:"’dÉ5ªèhF%HÏŽôkœýx24km"³Eȉýþ¯éjM¼.am‚R·öí¢¢Â:oæ8»s~½Ýj²Œ#‘¾nÆê4Z¥B¡ˆŠQǪÕˆ‰‰NHHÿÕ^qx–wÑÈï¹' Á^Iö’|7ÏßÓ콫Q«H¹íêü‚Ü(í«‚ |pKT˜Ÿ·PmáòóIp‹{/ET‚_SfÓËbáåöæ*•ÊBm”B¥ÖŒŽÑ¨c¢µÏ¤‹oÓTtð,Ò¤µ%ïÓ‹û»é)ýfI1ééæL:µ.F¥Ó©Öéõú(…VûU„UÄ-1Ýœ•NŸv3ÿ»zr¹;ñžøª)³h¢¼`•‰o\£ÏQjÑŠ§õcbTтϡŠÖHïZ·ù&þàaHÆÏï5Ú4Up¼™ß=fWRb‚BMzÕùëÖj5ʨ¦Ÿh®ƒü„Ø“a½«Aºa"”¬ËÓ)£51ÊU‚ï¢Ö¨5Útµec$9ÀUƒžöé‰_øfn$ºaNOM¤VR?“ôöÛÛ¶j o5ÚN­âH|‹—:1$íi)ôÓý«Åù…R¡R)£T*•:!!:)­ŽŒZ‡h€‡¨Åi°y•€µ¸ù½±·¨š¿?bz´:V£Û³wÝÛ­V{ËTœ×@½’ø5â\Býfù1óü«ºÝ¼MÆÜ\U´ÞJKy½(üiF¥ˆŽÖ©Ô1Ú¤ôk‚'²í}Êž!ï‹W’fR¡¶XÊt¿_LëoW•(.¥ ΆZ3Z3zvºZ«][Xhâ‹6ˆ›m©£R«^ç¸&ÁSiÒ5ruÅo¯S®).*\³µpMQ‘àŽl¡ÂÄ5¦6~•­Q©´µ:A£M£oÃÕáOž‚©Q¼nÕ[‡&ƒª-T¦E º¡xF=&5=A[x‹çL&oÍJ˜h¬ª>¯$üÏËË/ÈÝV²n[¡R¥yZ§PìRjÖüY ~Q t9J•JýŒ6Fí”Ô_ák€ùö7-÷r$!.¸<ÿ:F8Á«£5» ÉݼuÁpRHe¢Oi4å Ša ÿóJ †ÜÂu…krÖ¬ZµF¥2¾J™âUQÑÊ•&Z¥ŽVǧ¥šÐ¿nKƒ¤ ÷¤Ö¼?‰ã ÿx_”dÚpaj*yOÕZ•ntŒ"F¬ŽÖ& ZË¥&‹Ëÿ)‰7Â7=¹åƒ[†âCnn¾’—kP*µ…ÑZm¡¶°PYX˜•CFŒ%ÊÏ£ñ¨÷u«¢U*…†ÔTý·ô9³ÓRð—7õ/Rf‹î‹¥Q‚o¿'¶…ÏJ=ˆäQHòrùZ•:F10f´àD'¬-Ôî4ñ©)TxÌE93ùzîfN'(Fnn®!Ï Ä#/ßk0¬ÎÉÑ®Q*5JV¥WêÊUÊžtå|»iÃ*]´*F­$èKÚìKüw)ˆU€›rÿšUCÈ)œ—–måÌ’'b¢™†®‰Žé¯4z´*F=(.=U{D™ïŠQ¥&îFnTQž!ßPBbTy‚ŸAãT·ÜÕ«rŠVéWés´QJeTNT°B¡ÒDé¶Z‹|ù 9ÚhÕ(µ*=)=~^j2:8À1ó|sšÚÃÄ›¢IfcÄ uL\Ü”ôíV×¼Ic]~ÞêüA'(ŹQQÊ’muMMu7÷ÞX§ÏUê”ÊU9J¥^ø¦Ðë¢"•ºŸ›D}øà¿a•V5&~Ð3©ññÉé³f‹ïzÉlÆŸÜk"zÖEï⎘·àfIÊp³–רã 4H«ŽKŽVm"›vKy‘¦úuyyùy†\=Ñuy[êWߢ)õ:}ÁÏEE}îªUú…:*J¡(äv'Óá$\A~]QŽ&vT|LZÂö”Yófÿ*þ@sæÀç7¥ºHä”z{fŠX;ë"ýèoÊ£<˜˜A1ϨâR5 â¸*¾¸äcq]= NJòJJôuÍnBñVñ²ê&Çç’TG¾A¯,ˆz:R¡Pi ùû©©tD.÷^±b­J­Žž¢IŸ5ˆÿõ>ÏÛù?ÜNò9~µÜa¶=osÜž…JИhmܬèKÖµ4„GÌfþVNIÞ:A5rK¶Õ5>àìOú–"]ŽÏ_m0ä_J¥B©Ð­á,oÃs75Z…Z­H›27-µùÅ<sãÝ»×EîÞmì²à>çäN1)ÎY&šlôƒo¼¥ŽÑÇ(ÔÑ1éªè{V™¡¯õsØ®±NÀÄÛ¤=¬‰Q; h®<'Ç TŽV(”:±§Ðdjâù««ôšèQjuZZjÚ}³ý,D5ãúÊåË233—,ÉZ¼D 3kù[+¯7vÑÛݲ,ß']Üoï–"D«¬L7¤eþL¦º˜Ñqªg„oéƒbö˜n‰jr& 8ÒnÈ7äæl{ÛL´ÀôöVqŦ›¢½ÿ}‘º<©°Òç­Z½J¡ÔèŠJÄ0V^ ¯Ó+·¤ ²—žœbº'É6¬Æ¼p¢wW.Ï\’µ$“²¨4[¼BDdùÊ»®÷<8>O-x_\ñ‚KN"UZ#ELIsQ«éÍŸ Õ þªUŒzDÿÝP‚N&N1â‰=Z«˜¬ÝCÓœ¥Mðg_"¾²­âk˜Ldk¢ô)2r%ì;Þd³¾2"lN”BA·¿5KHjrrê÷˜Oåúòe§ŽYÕ,Ù +–Ú ÉBãÑìÌå.“Ó-éÜüƒ˜Æ®Ó‰ò-§>ÿ '|_Ááȗ棋:à« ON4sÜ›)¿R­)1(ÎÏÏÿ‚ˆìÝK¶n´(@ä‚eÈöúu[o1âÖJE¬’*ˆ(Œ$2fàœ;¦Ù3á®àmd”ž4Ç3š•㈱ÔÎYl4.îp™×!(99_Jy“Þœ%å7¦Añ¥'|¿û&¾ XLƒÐÓ?®R§…á~M¾B7Ê-ÈËÛÆÐS¾o ϯ2›ÍMÅ +VF=Œ"ivå%,õFz(Ê[~¢ª°¢WBôEÆÈ{˜MüœäFØ8¡ñ-ª K3ž6ž^a‹¬SÆ#¶¡ªÌÅ'§²HÄŹŽïyÛ¹å\Ó5¢'AâI¼FŸ.p…ꀼOLÜOÖ,=)ݽe “o£DÏ‚¡JòX)ÅÁZBUD:‰í-5ÂW©3ó2††­È6¬Ý"¹Ø<ŒëËÄ\¸àRœ$Ê!ùÄÁ :ÑL†à‘PYYâšx•£R¥æ»Äà / [þ¬ñ½†Ä„ ¶›7çòó¶±,²íëJJJä$¹.£÷‰ÂAnùú󦽆<19.¿'¼@¢¸KôBÊŽ B²a*xˆ»±d‰5UZ~ú´ÅÍÈ^xî´qq¶]ŽÃh\˜½„V]­tÝO@âtîÆ-Ñ™¹wËHNû2êBpi:­”Âö×’“úæŸijÜÜ”W’“cÈÕç5ŠJ`c]评ÔÙÊ{ÑY³“óòKò dLðëW¸êäÂ䢺èt Ié¢Ö°l>ìœqwùiÈ8-`]ŽìRÁýX`#ËN w•îXò–K»:6䉗9â¥RÉ^q9Lii1Šp_1ÀLü„&‚vüý”›§ÏUFÑõ—$…x?¯¤„.K¦Ý6•ÐÅbKJV³a’ÿ‘³-/_¯Ôëo‡gÄÝ0©êJ‘ N4k T„õ* œéƲ%v·‚6XÔ"»LЉRU9Jkî \æÒDG•­B/î±¢»ÀsWg§© ©<ê»æÁSu¹y¹«”QOÓÛbLÊÏ@—/)¡KnQÕÈ[]ò†z=O©WÞdz[Ÿ%{F£NN6ñÖL bUàë™öˆÂ±ˆCö¢“ÂÕ²æÇJ‚Xo/Yv·+4Î$¥'xnîÜ…BÉŠRtv9­7Öèõ†(e”ògâ<4N¢}~ò"êqä¯&Êpsu>JòòMD"H ÃWþzNႇÂݡ̀¾ä]|ešôYÉ/z%2ÞfU6ºau7Ä$x6£èUdgBqúÔ"K)®àBb̰]¹N9lOÒó¤u5|ųxÍô)ŠèH¥VìË`ø+»Å‡‹Èzú59¾}}¹ §“¼©*y?˜‘ûõðeŠ…·xs£ð’s¡+n¢y ±ËÏ—º5¾³SâÈ!¥Ò…hÒ¶0h¥+2³í niŽãèRš ?iŠ%Ù™ O©pØÔçf/Z‘Ý%>Mmø2¼Õ™,Y¡ –Úº¹©¤÷»¢¼\Ãj½>?—3àMEbñ”|ѼH” JJnˆªô³ŒÜÁô˜nÓ'¬ÜÌó¯³ôN†MžG·"R%“J¹´ÔÌìe‹[ 9mOÑ” [e/¢î†póÔb›DzYFf—D«ˆlÈþžTÕ§"XùÛ¨±¤¶‰#toê躰ù« zÑÏ`x.”ÕÊå‘bŽÃnÎ߇]qMyÁrêN\¡éê¼˜Š ¾´é/ÓH›ID‡ƒ ”Zp—œýØ8¤J‡“4pEÄâ$ [-:NÝ ák *{QéiòÐ2·WsT8d22=„³4u+tOGÅ*#Å“ºx>/È3ä¬6ôzÃbtI.¹ ëKËqóÄ6Ž€›ÒH«mù9‰ôù²ähØ«(JYHô…•‘žrž(‡4¤äÌZѸŒžÿO¶u:Žˆ)âfÜ8õ>²³Hºa´6•g¯8F!U¹®ÕY³M¾ôüOÇʨg©‹NØcÃH¤ª©(/Ÿ8}®!µúbóž?ÍqäI]à4_Òt«Ä¯¤ŽFÁ㤂$+RFkÄÄ ÃÒâbS¤4|$å ZòÖ"KSßÉ…§#똨G²iß8u2-](Æ©NK¥‘‡ÙY¥Fã9ÑûX²Ò¥?Õ¬³ØÉ-ç9©«’ÉY&8r­â'Nòî :ð»¼<½!ÏŸ«Ï-öZäÉ߉m~ù¤·Ä_L‰Ôîòm¹¹†½˜2!®‰ŸªÒlˆÖˆmã¬8ª„%·úq*hÉÊ%ÍN†±,#[”Žã¢pœ^œ-E­NÓ]‚µÂjÅ)£˜ü ÊqÝ•?–Ùd’‹9 Ž»/¥ÇåÌš:R+Æ—ø¦bšß0äå­ÎËm’fßÒgK3ÒóKJ ê¨8°òFó­¼üÕú½~Mó4+:b„e”ÑÏÌ$Ð…CzY#fª@ îZš1ÄùTÆ…â8uKühöÒ£¢Ãa\¸BÒÓbï_ö¢£F¢5Y]ÓȇQ‚åM÷)#Y1¡ çq1E1swT^• ỾP¬‚’ùñ$CÁ0~2Ë CQ%d[ó òòr£V­{»†êN Ë7I#HÒ“S|}eR·ˆ”_¡ 権€–ì—7'ÄÐê©c$òD›ÃÅv¥Ç,R*]9¹ˆl‘½€öÚôs,rišÃ$Má¿•KEDÆXƪ®Ëø>ˆZ½NNå³ÒxóKË_ã¥û’Ç!MVÐ ­¨j^Ÿé¤¨ÇV,]dÉgnqE¶ ,tÓs§ËìÆ­/wá¼Ã0ÉãH×”Ê(1pä'žÈEo ¸Ä°šÌ£ÊË/1äÜš8òâðuº½,PÎOì;=*J÷z$+—‹© &pµð\qµ?9‰±…‰±.ÆL_—ZG`"ð‡#ÓâgNGf©%¡al©'-]l­ËͰk8we~ÜO\½u†Ÿã+­ÈÁÈXÉã]iý V®/¨³.Ü'n,vr*p+OÆX–â 2ÔÉ`ýW¯[—»NZV–¼(ë+Àâ%qb¤YUš¹+ø K—fggÛ«¨Ó±àøiÇ˲DwƒTZ•Ú*qËqi–xùtö§üeâŠ$±1Ý$¥»-_d¸ˆaÝû´Óƒ•4C&)E SbÈ ´,Åay€>'²$J@»8†«™]Ÿ(y"äÍ?((–ž„ä8Ø;dÎTéÂ…YÙ¶Á*" åÇœ Ç1ÉÝ ò’Õb¦îŠN»©)â¥eÍ>VR©Øö»¢[RŽCÊyµð7ü-ËÂ6?B¯è Æö.Q…X&ªøi­6=ÕWš—È¥4†Ia+ò榨(Ûl<‰Æ¬¬Ò“¤¤ÖxJZ§©9)n<í ëC­UÙÇ:ïrÌ/-rÁZÖ}•ÑÕÂÍfëBÉá3Œ¡À²Ô«¤¬ôdÿ(½^z¾­|ôˆ ÖÆ¥ÏjhN›ÂiØŠ¥•XbË!Z8–ÙSb)U¦8³êt[i¨"#I¹(ËaÏýŒ”„NýÂ5"ÒÚ±ŒeqÖ%óïc-y‹s¡×±ÍKŽKê"1ÉÉ#ært:•žš,åHÄ·7ùŠ"#Û“óò2ênˆ¥TGÑÅ7Ÿn«ree‹Xtc¡ñt§z9~H! Žsܵ?ÕKm܌̢¢#Áú†ñü5z‚gmzzƒµ&ÁY«¬øæ¼ïÇÈkžDÈDFŽNHKINNþž§[±¾dg\b䬰e ô³øÑTº/F〠×Y˜‘±âˆ(ÆS+ hîàx¤n3–.^±°´táŠE™D=5Oeu¦}üû”kD8øÙ)s}ÉhB²’,QEð'3GHA®¿äpó¼H)²e}‰°éð“Éü¿¾äa?/ ­·ò ð{/G‘ðLZrÊ,Ó ø‚û6Cl¼4ãšLpä:TìX¹HtVœ’jkdd.ͶLq -U‹É¡œ,;rôh¹q4»S¹÷é)›¿4‚dJã^õõgdŠ"¿Ø&:ä¾´™É@Fäæê ¹dTU‰AY¥/0ä¬*X­Ôçh”Åú5EêêÈónÖ™¨üÚP§U¨´q©sf eŸŽ1ñbìœa^f™q»Ø¢Æã¥™Öʪ‡;§ŽJkJêA"ÏÈîÔxué´m"¦¸¸h½R¡Ð+•Ѝ:ÛOÿ\™ˆkÈQŠãFò ô_‘’UŠ5«t¯o(X÷Á^βX8ñêëîñu7T74šµkU©ÉÉßÏ’T£Éò’ÒËcý pÎ]›5üNZãOÆSGNµI8ÊeË)ZÖ©º*îÎ5¾ŠÇ¬tµ2Z©Õ—èc…&ó¬&ê6ÜóMA è\C2j„(H^~ž,Ê‘—_¸z]qTѺbAf¶®¡êÃÿ’Jô£I£Y£‹ÿiMÂ.mBÚœŸLwSþDßîÞ†ty'S –Â5üvNY™ÙbÁ?«v´1ÉÑâºÑx$«Ó«¸ädÑ ˜›UVv¤t‘X–Û‘$×âgnLW O+ú¼<}T.=É[b`ÜÍ\ƒÞP W ÿôzC¡¾P©Ô­Ñ¿}O|k¬¬ž^ÿ>}ך¶®Ý¥Õ<3/Mp7ÌÒã–Æ÷bYï÷³%Ûsïå£Ú.¶«q1Ûëv,ÌÊn1·#Ùñz)UÍýJãTü›wSã¢U eÎÓ ¥ÒP¢—R‚bX®ÜÓ(•J•R¡£5QÂýÂ¥ùFX~+†¾¸†¹éq:]”F©Ó& ‘’<Çã¬ëm„«àÑØ-Å‘EG†,^päT{ÂT'3–.m¥_”Ú­æîxFMŽ`¥Þ`Ð[Oó–s¾Y8ÙÿtÏdÓ²g}D g™êtš"E‚n´R«‘œœü¦¸ÅõöÎN½ô|«¸ŽºuÚµ¢oqÓ,®CžLãO&ýV"?s÷Þ+\¥Õ(×DINNM½Ÿ’JŸ~7e¶Ý»ÞO¹H¤Çô]^,Ár‹p,ÍÌÎ\”±ðèñ6¶píËpO–•.ÌÈÊl^ ªcÂ17ù*U€´4á[rœJ«R*”úß*”ùCŽaÛˆ”KäQsî6±c¯J—D=†÷ŠÅðÕ›³¯Ñû•:þ§­ëJruº·7'h µÑ©É#~H™%†±fý`—Ï0'‹Ó±vçÝ2Ã$à‘ÂA'ÛÍ(;itßh›Šˆëyê±x‘8)·ƒCG¤Ìõ}ÁãH­ˆŽVê•£ñ0äês×ñfLâêîI›sôv}£%†ÕÈñ|S]Ѷ¼’m[£ö®-Ú¤Õ&¤ÏNIþá>G )bÿ¦œå^®ެŒ¥‚fœ¤šÑùÀSGJWÊÜŽO«GÒr#â⣣£U‚p„GGë×) s×í%¬Zžõ¥s>½07íÝfØVR_µ{“&amšó’gÝçxo”[pæå+Žœ² '<íNJêq4£#ÂAOí÷nPm0¥Æë¢ƒ£µÑ11‚ß¡Õê•úüÁ·0¥Ì¥›Ýù^ +Õm¸Gž(èB“Z]Tü~qqAQ”®H­Ò%¬Ñ&¥%§¤Š’‘v­õ[5ï‡+aaW¨€G²,‹&6©Úéoˆ.ÇI’îX±¨ãóq7¬Ë¥—o>“š­þ)51Ñ…:eκbÁµH»BJ\*Mp›÷*yÓ–[ñá£##uEź¢éº(݆øUErt©³S¯¥§‰:1eŠs£:PZð*,ÁrÒ‚AR Ž£áªN…©HUÆ2šäè¸pÐñQdn‚*:xJ4%>F¡Y«ÈÍ-)¸ñ ßHkm…MšjLõM[ÖŽŠRŠTOMØûzü¦¨ EE«ó×$¤%Ïy“sêcØRËZד§!֕³3¯(=rìôÃ}£3Ñ8yì¨àgdv²ªÊš² I •B£ˆÖFGk´Šèè`íÛdéü‚Ä÷#w7mx¿xë–˜1êí*….X¥ŒÔEn-ŠŒTèÖ¬Z“[’[°)}vêŸhÈ‹ã®<_h³ù¤z< »>26„¨Ç©“íËk”-]a[Š›ÙÙñ¸\j²ð} Zð5´ÑÚ•*ZQX¨P(•Q¹¹ùÂÿ\ÕEQQ‘Š„é‰:F§Ùðvü˜µÓÓsr Ê„iÉÉdùYî§Y³h—Ç›tÏ×½ýž£w»ÈØX«€‡°rI˖壟,hW¦¼láâì¥.ë'˜f§çûKqÑÏ(H *F­Qiƒ5Ê(m‘.*êýßF=­ŒŠÒ(½x&aÍÚ½·nÝøùFúæ)))©³ÒfÇ¥ÌNNI6“Œ¸ÉÒè÷ý]Ñå¸á°5|²p0옴G82—f.p°l¬ñ¡ù²ŸUeïso»Gh£U‚nÄâ¡ˆÑ Î‡F£Ñé¢t:¥B·)!¡°ðƒ½{÷ÞH˜;/%9eNj²Àœ´8i䈀¹M}L èç´r˜¹¢ÌØÌøÑ¬óªîvþ‡ã®Í‰Ñ¨ãTªháÿ@­ÊÕ¨5š„xµN¥KˆOWÇMIM“2'-}Ë 3gMd˜,‹q´­s£•p0Ht€3l–_*ˆFVÆÂc\’£l‘r,sŧvîZÚì´˜9ÉÉ11ju¸Bø§ŠK˜2bЈƒ’SRRfH›wG3" …4äðn{Þ}r+á@¢œbY0;kEiéQÚ ØÑ‚Ü#¶ëq,YNu\jjZjj\\œ:mJzÜ Ô)iq³R’“S„Éßs´ãçÌÚMŸP÷u3.%ÏmÇ»Tµ$:À)â”ì–þñö·ýYãU m]Ž•®ùñLkî™R‰FîÕ ¢äjê? ‘œr— ÇìºHùÍ¢_è³î'¿ÙŽ#õa”‰p‚˜?úÉhKc‡ñtF³n,ºî‚Ÿ¶sp|#gú~Þ›sg%“z)ákö¥éaîšIœ7ÒHSâd'3ßî¡Tµ2GÊD8ä® ÙGÛUEe»Õ©SÆrËÆÆ²¬Ž¯ë3ßœ· æ_‰B_›ã,ã›§ÏáÚ½ìU¹#åC¢›—e.]a´ô7OI?r²m)ñ¬ŒÒ²ÓâÆRk +R÷çˆ}{wÅU8î AJÌ 4ŸÁý\²•>ü½¸Vù­â&Ò¥Á}Ÿü}ûߪÆa´ ‰pÄ[K–”¥8yìø)iä”Ñx|Ei]Ž…K³³—JRQnvf–KRóĶšÕ(êÈŸÄ»7ì¥&]“¸Øë5Ñ).•0åͼU}ƒD´ë™‹‰ƒ±bQVæ"2o¤ìø‘…Ù+Ú˜'7ž\œMÖ)“$‚â,\”••qt… º8h‰­4mʾØÖæq¤®pîZ‡ÖÝ0Ïp¨]­Ï˜Y‚H”JÓ é÷ìììEm^uÜxD|Þ“R¸êä±SƲ·\ó³q’>˜lï° -õĒᬲbî —p…u¤èè€V¬\a<ÚbÔ”£l¹SåX@%giÆ)K¦ÃxÊ5U’ ÜŸu‡^^J¾O.“gÙmµaÝ ª)ÿÓÉøš@$: -ÜÍ á&;ÝXatR\el}Íxr1•ìÅǥɑEË\–hLSÝJ½qÞ­X¨+|ÿiõwt«Y)\+Íi?õ“耶ðÖ‚c¦–3ZfP;it’Û°.úd<">=;3£ôHÙÑÒŒ%Ù+]ÿCr¸ß% Š;ItD@9ÀÞåXÔ"PUj-Ë]¸ø¤³ ÕQKӠѸp©uB"]ýoYããþê]#Nr$: …ËÑ2Pe/ËXšá¬3Ðx:c…%«Ñ"Ôµd¥ï ttÀ£1ßµެc’,ÌZš]ZÞj½?k„j颣bÏ pÕV7¿ÃáRçðhåX¹¨e Š¸‚‘-­Ía;ñÐá_\ôÚæ*™ÓÅa1×p<ºŠ­m¿õ fä×;t¬ ÍàNü¸~lÓúÜ.cÆ®ÿÑdð8¬§à0ÑVßv3r„kÌà6p‡Ÿuº,ÅïÙÃZÕs:˜ŠÁâ°î‡ÃDGU[Ì(à¡fÐI3¸ ‡Ÿ•3Fþìá¿ÃU ¦ò,%:&»ÄŒØN˜ÀMhZß[Æ´ ¶÷ú¦Ž¼…óÅaeUøêž8èè|„1mµ£šÀMøeb[vÉí˜Øþ$çE_,ëÌh‡pü2QÞåfpoc"Ó~&¶ïã"‡õTZ&:&?I3¸ ‡z0!àPÛß²a†@,ÛÝt£ífTþEk3 A´ ·…k˜~©¢¼íT|ت´2ÔQÖ¢uÃx jp½ÚŒÞé 3¸­ aFž(o'Ƶ*Šqð>­z8&×cç{³}ÑN3ú¢MfpZÞ¿S^QÞ^öË]ˆÿ-‡íFf$§ýVÔ&3kà>Ô:é†ÅaaFí5#ìZÜÛ-ã åã‹–öÖ515L奘ow6Úi5£Þ4#À“¦EFs\Gu£¼¼¢EYLhë7« ³,‹\ï6£òŽóh3>ÏŠÝÌŒÊaFx3M¶sN¸êˆ·2Èž†ÁŒðÖÛ™Ÿ•»»bÊõØÑÝÈŒ>tUÀŒpCz»¶„²™í–òÁކÁŒð³6Çåç®<âËm—U`cW{µÉlJ]gF ÌwàYͤsÀç¶Gü³ØÕ0£Î7sÀŒp8y—9åå¶#(ä˜hèÍfÔU‰2˜îbèªÐt«ð4‚ ÝÄŒBaFt£û®>â+d€ÁŒð6lŽIù WñvƒG°³aF7#?ìlž4?ºr ö#ÒãXO¡{˜Q9Ìïf}×tm9lDóÌf€0ÖæˆüÂõGùK6¯?»»;˜Ñ‰r˜ÞMl:¤ x»‚$9`F0#<Ÿ_ºb ©=0fÔùº*˜îƒí ˆýÒAÊ0ïXXÛë68¹»üć/„ö`Bß9á¨yK†|o5#Ÿ2£wFØ?8f€ÛpˆmÝ6Þ‰#Þú¹0à3GÑi¬í­fĸԌ$›yÇq’ƒEv€'‹íâ;?âf仟ñù;r¦Ç–SÀ2<ÞoFr˜Ñ‹ ú¢ýƒ6fÄÂŒx²ØTÃô.ïüÿÂÖ±t/8˜P÷,v¸÷›‘ „ã³Ï*Z>ø¡eU¸ ¡& µõˆ¯xè$ìËÒŸ¶CŒ°ÃaFŽ„£…)U´zNÌ÷¡·ƒ¾qG¼õ®wFº]ñn¨\òn…Óº{ËU9VáµÏŒ*ˆM˜î‚Mýý í;â¥ÕuBO8‰R[<ŽòTàÃŒœ™ÑKÎͨ…pÀŒplÜ—ÊÛsÄ0ã>ûâ³qN&U4h·™!Ç÷Nztµ˜nãðˆé„§G¼”ü~Áñª=ïØL,²3„3²7£š‘sá€à>G|sŒÁ'G¼4;û„ÜQ§ðgr›5¡pÄÃŒÚ`F/B8ðÀ#þÅòöñ#­½­g} øÂẟØáÞoF.2#{ᨀà6ÈÌj[VÓZ>Õ£åÿEo¦ÇgNÃŒ:gFÈqà®t¸Ž²ÜîÛAC!ŒÜvI†Þ(‡éVfÔ>ᨰ‡üÂ3Àm°9G¶åˆé‘O„¶XÊ}ÝËŒ*ÚeFmõ8`F¸ ¡m9"·Å ­ƒÓ¡ö…¸¡ÍóQÑ93z„sbF­„f€û0¶mC,Gxï–å0=쪪**ƵР»YU2Ǫ̂©õvjF¬ªÂÈ3ÜÛ鸟9ŽqLÂù ‚Ì(}hÇ8›BþÖk·a¬i·7£/,£nÛ×ÇQacF2˜OÛ…>t~Ä ‡mèg_|þ`Ûò{¢U˯pxÁÒô…õ8`FÍè³f$vŽWØÇý XþL¸„àvvÐóëh¬©4RhdsV3ÔÑ!ÛÊýfK·ÁŒÚeFå5#fÀ“Åv±èЇñï†ô‡¼SÑ\Ž{⥆i9ÖÔp`±h˜‘õº#3’ ft¢üáÂQ3À°= O”»Û¤&êﻃÉaFx;cD§]É»(ªêvfT3À»YÏ8XJÁ…ØÆ¦×cwÃŒ:ÆH˜nĶ1×ñ'l~™±»»…•ÃŒðrº4VeÛÅá‡í½øÙ$9ºÖŒâàÉó,ã`@]—„žÅΆuˆ ˜î…m ¾Ëëªl‹aP~ßmÌè ›Ñç0#Ü NÞr¹.JË9ììnbF/¹Øá€àÎA§Çí>)"Ä3‚àA†w»ì“"B ^nFlW™Q9Ì÷£·ígÅ]•áÀê;0#˜^ƒmó–KÃÓ¶µ0hÛ‚ÁŒðšzØæ?ï’â{FÞ„íåf$‡а]†§yéñNwûÚ¶b §ngF.JŸè3À±Šíºöñ—ì^£°»›UÀŒè>Ÿ{|îú”&>)ÂŒ:hFr˜îI“]T)ÄŸOô¶;‹ 4Ý̨‡ëÍ(f€»rÈÎ=xÑÕµ÷X%º[šQ̯&ÔÅiûÈt(v0̨#¼3Àù;ãÒCþ]û—à ݄å]iFÇÀ½˜èJåhqÀ#¥ 3‚àýQæ3—aèÎfT3À‹¹Í¸æÃb…ýhCÛØ¹Ý‡íÿöX f€p¨ÅúnÇ>,žhñ™¥0ÝÛŒ:¸ÄË!0#<ñif܉HǽD¦aFvfT3À{Ûâ`íÝÞæßŠe¸dfv+̨“ÕÜc±[pS¸–Q&ùKíú´XñyËøŠ…>aFLų̂ü3˜Ä/­ŽØÞíHn~Þ2É0!J3‚àÝÜnuÈ3¡m+ÌýüÖOez£Æ›1_Œ‹ˆ¸èÀŒzwØŒ^ mmF!0#<ìâpÌ?zéÏZJÄEo犯øg¬n­¡®4#èî®y¦÷;Ÿ;χ—Wìqt¼3¡Ð /†›aýC³µ6#bIûCaFxê a¬ÃÃ7äEÇ+óœøp\‡O`Æ"¡éÅ4DØü©ÃêÛiF¶ALFÂs©ŸÔòïèнì"3¸%fçáª6á8¾` ³ ‹×b7{(µaL›„ƒç»ÄŒnËÿ´ú÷¶Ä7Xd:<“‹K“£ÜØé8Ô£CÇ{gaióä–g„«<Ð.f8ú£_tþW›À­iš(ï@x¡Éé뵊p \åqØUá6GªêjFí·"ùCÌàÞüÒNéè1ñaÍZ­¶g/b{ÕŽþì²Zך‘|"zþðh¯c}ï6ï½×?ôcb«P W™±=‡*ÇøêÇiFOàð³mù¼(öð£^袣ç!\å1´®Âÿ‚5.4#æÑfð¸ÃÏ<ôhxöpÝõŽãWyªp ‰õÙŒÃëÇ:>êÆ®ÿ±­§™ÃWxÕ„Ýëö˜¯°ÿxU0£Öf k<‰_š86´wÐ;tìÄC‡Û®vüIá*÷w:Vá:š¨Þ13B6ÐÎpÂUnN½Ã*\&¢»ÐõŸ\';v:Ð èÎ8®Âefàx,8‰•#\å¾|ìØMÄÌÀãá*Ï¢>ÑqzJx| \å:ßö*\peWyÊŸÊqÛÞÇØ3÷ø‹p•»9‡.¬Â€Nž‘^E¸Êýi@.À¸ˆê*wU¸7ÃI¸ +º UøûÜ TW¹3NªpÛ8 º4º-5ŽýÁTáž0¨®ò,I¯Âž~~ááýû‡…)ú†‡÷ìÉÈ–eŸzÊÛd³pÝ‚®ZP¹C„#¬o߀@Q8úá`¹ fẠ. W½1ì©§žbYFæï©V…‡?óÌ®];wîÚµ}û®]I7nÔŇû 02Ö‡ç?žß¯§—ìGTáº. W öÔož^p%"‘±1£ö„#]ŽÛ·ïØ%èÆÆxuxpL8Ä„caOï® u€îDçÂUoÌ2$$$,Ì— ë7&^?}zRRÒÆí›öìLOÚ¼}Ú´{Ò÷Œ$,¨ ;0bÔ¨1¾=;’e½B7&a. {álvU›šˆÂIJaa:]¼núô)‚pìܲsOÒ´Í›á”CŽ‚p°lDDìh"¬¬çà‘#Y¹ì?Táº!\pè`ßÀ¾}GÞ¼1>~WÂŽÍÓ§ÇOŸ6mcz|ú”¤iÓ `%¶ð«Ó¦M›¿cǦí{v¤ÿ{PpXØ'ŸL˜àù;ï*ªpÝ‘„«Ì<ßo¨àG„cãÆ¤ø¤]›á˜.GRBzR’(2A5hùmpð4A9¦Oß±cûö=éééÁA‚püMŽž¾ëŠ®o5Œ àå´{vÕo~Óˇø±#FÅŽ#xºéñ$$%8¬¿ß¾Á}ƒÃúöéÓ'&fûÚ{¶o߸1aÓ´;w&l‰dÙ  Ã—¿ªÜ¿oß<{ÇÕ¢ Ð]igu• ½¨pÄÆNJŒ‹×ÅÇÇQ ÂÁŠÂÑWÑ?8XExúÚ=‚plJßž”¾içΤMIñ‘1 ëëw¹´²rß¾ /{ønC. ûâ$\ÅT;ÜÚ§—Œ ŽПTL®ømlìÀðØI1‘éñÓvî\»k»ÀÎäûÆã"#ýüd2â•°l¯^ûÛÈ‘ó×{ö^›Œ*\@7ÆIu[ëX8yøÀððØQÁÁT8F)‚ŠØXážI‘1 é¢plß. Ç&A86Š%ÂAså,+ é#Gèü-ž½ÓQ… èÖ8^,;ÐQ]é!câ‰0lÞ+GäÜí›7¦ïØ´q)ÂMÚ³gÏÎwìØN*r§%Ž î4`ÀQ2Ä2+¦OŸƒvøð§† <5Ô+„cÂT€n…ãpÕE‡Â¡C<‰Í›G!ÇæôÍ›wì˜F…#)iÏÎ;7&íØ±y3‘‘1c‚ƒ5çŠòÑ·Y8~3têðaCŸòÄ=ÖÒK«2ÃŠÝ ‡áªÉ7eYÒ³‘˜HF2ÌÀp2Td§àb$íJš¶qûfJx°¿?kõ3ËUZyÕ§ÏáËŒ=Þí„ãŸÛ ։꾨ÂtcZ„«]¢+ùÉ>ùäÌ™ãÇ©6ˆz@¯2 Û£²Éq°t # ð}.rçÎ;v ìØ•”ùœœu£!ëÿlå_þåÑ»+",,â Ü @·Æ.\æxÌ+u˜@I8ˆ>ÈÄDFsܦúVú¢~G™Œ |6rçnQ8v¥ ÂÉx¬p°«®b4´õ¤#G†ŒýòLeåÙÊ_µAŠHYµÃrÛ¦´Šy.r´:>>)iÇŽM ;vlÜ4(ØÇÇí‚TÿbÖm¢Š}D…i@€ ò±c+)/¾l«VÙ`ìZ7¤‹ÈÈÑcèöI»áHïá/À|…„îVOL$ª¿}rö,ŽãÇ÷;;Ñ`ìDDú>91~Ì®]bˆŠtpõ è5Ø­º7,!* ¿ÿ=Œ\Á¤D– øäS"çˆpLl¦Ë©À Oœ7FÐfáЫ—[ýbp=={öéCZû>-;sæüùsçÎU~ó;—I__³j4Ǧüûúû‡ÇOOÚHúÉwìIŸ6jTß¾¾¬¼W/·k·— ¸H8É0‘O/œ©$ÂQI…ƒÔÙZUƒaý-‡¿@pðtA86áHJß‘ž4z´__¿@†q3o®äãO eé2°Á1Ó§M$bäØÃ„‡ÿóð¡ˆF&µm0}ú +bFщ¹Â™¦K—íߟlЯg¿~ö¯?ªÈHKÙ€p@ÇYpx(Ž˜àpÁ­&×Ç ÂñãáŽ>|èãˆ>>2F&‡RõéC„£oð(‹p¤oßB…#^ް~ý†Ø¿þÔ©½ž|m„\Űa}W#2I€¦¶_ŒTпÿÈQ!!£†“%ƒÃÆ+bF&Ór7o™6-}ÓæÍ7Ž£ŽM §uVò!¡ö?þl¤}ËyÌ „ã_þå᱃€Gñ‡a~~áÁ‘1S¦íJعs÷ž¤)‘‘£G÷ï10$TÁ ªá£"‰pйÓ6á˜6Z7zYP c ò‡??þ\+á`OŽýû÷í‡pÀ#xc˜ÏèÑ›7“…`Å%awí Ò@ kÅbwìØ³sS½¶kÇ.’'k&M¤%x`8 ^õëwµúâ·/NzàÀ×çÏ»paê¤À éõß`e½ôâ+¯ºµpì³Fcø>=Ô1¶Â!ˆÃÆÍé»,±yÇŽÝ{6‘«ÿ.Y˜cãÆ]»¦M 'á+šïW}õÛ7á˜üáóç+Ï••=ÿüá8tàÅ—¦¾âö±ÂmÀl6Œ4ü1¬ËNž¼eóžÝ;bWÒžoî ‹ÄNÛ>&nÏ¢s÷ûû÷¥uU2Öߟa~ªùÇ?îÝçÌfžã.^\HSÇÆŽíߟ¼>ëóQÙ™3îÿ» Çïß,D.þ¯‚Uð(åø ¬ Œ Û7ïÜ9gORú®¤;þ²gÇîÍ[v$™¼›GúܹA}ƒƒü{ˆÂA–‰•É® ÂÑÐÀq‚™á˜O…ãìß.ŒÙ yyŸO?:sfÿþW¦º³pìoxÐ6 ôò‰ˆ K4ùûÓuþ‚|Ŧ?™ŒˆDHHŸŒ;*,ÌßÿÂ…ÊÊ~ýjn×ܹSsûÁ³¹ªÊÏ÷àA2ª„|õëGªï?x¦ò«Ê}û^9DÞå©§žØ/è}#óo Ük°Kø·Á‘x>ïîcÙù’p,xƒÔÿB8À£><$äìÙs•dÉXÑë üǬGeå—Þ4sô8ù~ñâõg*—Jv¥o>> #cçÎ%MfsíW^ùÇ?Ξ=xÐßÿÓO}Ú§ÏáËfs/".]#ð/ù÷´”ÇÃF àá8g޳ÂUA8¾&×¼ü­pÒ'’!~„Cð8Žÿ‡u=YQ8èÕÝT8¸úÚÚW~G„ãC*Ÿ–ö9\Êó½|Ý€p€ç4 ¤ß—_/CÌq´öÙ³_}UyνþÐåË\><0¢úêíÛf¾ºúå—Ïž½pA,Çmnd37é÷xð€ç'Of˜3gÄ–Àýû…M™Áƒ‡ #3²\üÃÿå/’Žßÿ¾u).€ŽÃ5…„´rÕF8.X]]s›· ‡¨Ñ.e [5_ hQáÓ-ÁyÙ·ŸhËàÁ]R’ á€ÇÍo °o¿eÒÔÙ³¤‰ïìÙ/¿¯}óÍ;«¾,ÊDu5Ï_½:ñ‡¯Ÿo+·£ªÊDÆ’²ÝÉr¶òÜù¯~tà€Ï€ÿãtÑïH8ˆtØ ‘ȸŠ?¼´Ÿ$g[ ÇËëWýýïb׆$X?Ÿ±¸Œ˜ç W«ªH¨Š4 N*cI_ÇÁƒ!à] Æ0$8%†ªH ªò\å9‹|œ?à£Kß‘‚["áœùÊŸ^}z0r«Ç!æÇ…ÿ¯½f6‹µWÿøé;þ•ž=_2¤Ë~|ÇÂÑZ: à*† cYQ8ÎIÂqŽXY…ãÐI86‚ÇUŸž½á°R Wåò×^ã©¿! Ç…²W~×k0„¼ŒÁƒ}ÏI†eR½.HÈ™3‚p¼<€ ZãQ5µÕÕ~¾r14%:6 UU¯½Ö³gXXUoîÓ'°ï ã?ùä™§†îòj*[áp&öÂ?5¸J8 G¥$È:…¨„#ЯY1¤+T6ä=ˆpÂñ-Ï÷éÓ·ïøñŸ¼ð¥ ûÔã€pÀã¤gÏÿøQ$Ä”øñã#Gö ìNäàÓOËŽ âçk‰JU_e匭d0Íáªçžã¸††;w䬨KþÕWçΨgÏ.ýþòçâÑ,–kÿöoÿ«0h·pœ³Ž>}áä@~APA8¥,8{µš‘Ûd6Ûš\«p÷U•_£ €ð"ÞxƒeO]8wîëóÄë ò±?\HÖûûøãW_™<9"‚ F—Y’rK®Œmî¤ß™™3._¾rUxTvåÊäÉ,{æÌ¾}^O6i¨OÏždzîС“†þ”ðÕEâa/ ö[ýå9Æmaø0–½pŠážµÞ\_?óãWˆp ôñ‘1D9¨:ÈÙæ9# c-Å%÷Ìœyéò•+ÂuÙUA8öÌ—öøhß>V6LŽ~‚pš1|èSÃ!à‰˜ù7F°ìßþ&–ßVÒ…_'üá•W~­å828ÄÌsæêê-ózʨ>Lžüæå矷æÃ­©qrQUÕP;| ¹4cFXË ïççÈ·‡ýnþ+–wtñCgòÑZ8&hƒhoó°7š…CøªüúË ^}µ¶AÞL„cýüÞ=‡É“/¿ù»çmzþlæUUUÕ6DDc@ЫT8Æ ÂáïÏ2‚ž&­Çax½z 2X`êðù¯ùÂqö¬8Øðüùý÷¬¹ÍqdX!ož1ƒŒá¸;wª«gÌ ·êëHþÂ&/N¿¿öуÛ5aa¤£\Œeüð¯ +gžè¯úß)áxx° ÒN…ÃgpO"æÎ…¥Âñ•$öëWs3 ‚!HÇŒ×$ᨹzuÆŒWgp$áÙ ¡Ðžq¾æ¶àiȉwBÔãàÁ¿þuìx†…p€Çùüó½zúùú÷ê@BUçÏ)PöQŸ>Œ¿œí×o`DXX¯^aaýúõ ìß?(ÈÇgîîÚZ^—˜&gš;Æçoá¸Ú†W^½XÀÕÞihxþwÿøÇ™3.L˜À>.áøß”Ö²ñOÿd‘‘‡IÇŸÿ ãGD ÂÑ'À7ÐÏ¿wÏ@áøô¯}ú1è×3BŽž½IxÏ~={÷ï?@ŽÝ¬­%þÇ% ëqŪgr\CíÔW.^ìÁ ¨­mhøÝóT8¾„p€WÀ²cÇùå×_ó )ÁGîßßoä³cCB®ݦék³ÉLòfþΛÕ,ëç7i¨Ÿßá˵wjjIŸÇíêψÄL}åÁ’m¯ý5|àsÃ<¸}{ø¤ÿ÷ÿ*+?: {<²ñ¿mh)-…CÒíŽ!-„ãëóïîï6vlHϰ°šAÑ0›ˆᨮ–3C‡úÉ. ÂA< Þ\]ýœ$ý¦N¦5X|Cmø¨¡CᨙD…ãÀG,„¼¡Ãö¯­¬$cÓÏŸ·¬ÃñÀáCßV=x@üŽãéÚ5·Ÿ{NÆLž|õ*o¾rõµ/VW×ÔÖ׋k‹“ê«Ýß’%ž8îöíÄÄÄÉd¬ú·—YöøñÊÊ‘#ºü—ù?6Ø ‡E6ˆp8z„ÚÎðᬠçÎÙ ÇׂpœÿæÐÇ’pðbAîƒ×jž›ÄȈp˜ÌW®Ì ÂQ+yŒÈ‹™ŸoŽšÄIÓÉý—„{ÈèÄ‘#{õìò_ƒ‘#Ŧ¿ÊJÒ1~æÌ9ºÇ¡Èò¯bîƒõœ™«m “1ä^ž¯¯§KÃò$€E®‰·¯\e˜êêúú!C¹IädÏ%–=ö·úw} êÿ´B¼ÿŸìpü\´>!#kŽJ"äòüù£‡Dáà©pk µaaŒ¬ªªQšõ âA…¤´È`CA8îžñV.]"£~ÔCá/ò9úõûæ›Ê³4PUIªŸ­‹áG¿Ã¡¼œW Ç‹éðÕø–ùÆùrŽÞci’R÷¡p\I¢ÖÎôtåØ7 [[››i:ƒÇãÜøâd¿"é¯lÇGvƒù~8²¥»Âð½3:ÅÕê½{wﮯß\ +«þ˜m\_—»¢ìUGÚç‡Â'Ñ[šp¼ÃÃQ|æL£®r 'Uݽ»¶v­qú|WÜj…YDv'G6ï¨Õ 锣\êëû`||lô½¾ü•G±˜Ÿ’²þò­ìåp„?þ÷O3¾¯j`8ŽÏžm4Ö³pÜ áøÃ—Ó“ýýqüäŸáÈæµZ:ãˆJi8>ÈÂQ­F…|õÔÞó°¿¼ÓÙÝý[6߈£·>0áxC:##½½Iùì¹ÆµµµpàÈÄDŽÞ»W,öô<Þîd'UåïíÍÏ ?¾ôÉ/—êõúÒâÒØXövRì-—ìc?g’ä Ûó“ªÚíüqUŽÚO,¥êKŸÖÇÆ£ì½ÇÇÍü¾ÀÝÝ?оÛõTÂð6ôôÄq¹<1ñeãöí«Ÿ7~óÅïîÜùíõ(º|1I„ÉF¶Ì¶µÓnoo÷ âññ,¿®×«ÕŠf³“=ΚœŽãááÎÑT‹ÿ ªÝþêéxõpDQ9¾2ÑhܺýùÕFcõ‹pl_¼|0Ûá÷llo ¢ññO–ê¡õ¾÷ÊQm4ÛÙ£¬éÉ8î9"ƒ€7+œ[­öõe;ÝZ[»qãú£b1\›?†Ê妞>ÝÚ*Ëå¾¾ÅÅúÒ… á¬õp¾ú£‡Ç*Q|¤†téÂðÿ^rW^Ç_z÷Ñ]Ø[uÚOž GýÂÏÂJ«ŽûJ¥$:RC€7mf&,¹M¢_[]»ùõÚÚÕ«•R±Øzns޽lyngow÷þ7CCý½ƒ'jµbñÁƒÍÍr:_©ÕŽÜ€¾-íöÊŠp¼¾‘Ž8î>7±zóëÕŽRGk§•_ë²ñl/¬žúøþÐÐ*ƒÃµZw÷õæÖf8ŽD8~˜ñøÉðO‹Å°#¼Ñøì³þþÉÉ8š<õða¾Ÿ#¼)žhøxs£ÙüóÆÆÉ¡°®ªTyçzŽ•áx­pŒœHù–…ãÔ©Ž­<°pïYø}s³ÙÜjnlœ:"Ó]€¬|ÆÐPî‹-ÌÌ„½àI455;;?733??77;7;[«EI¹<0ðÎsåÿé¯_‘4…$éšš wþEIÇÜÜÂBÇ\GµP.—„€—ÌÌœ>DÙ¤#»r<Ê?GóóïüÐdàMIñclŽBv` üþºœ°|endstream endobj 30 0 obj << /Length 779 /Filter /FlateDecode >> stream xÚÿü      *3*$(( 10+&6-07((&9&>779)(=&44,P.+ZF \ 6I.M+4n,f!*P@&LoO3F9L"K+2o3o&5TYBYLJIJ)HW)YT*RZ;_P-LR8jgNq Llldlk-Qk8ht0fMC WHWKMT EG?XR9Xj gUjWycqq qf(]U4EEEYXYPHTRGyVjJvEzrStkLekmOkkl```wxvmsixGF€ÿ X” j©3RŒ1f›hŸNWŽJp§hRŠvrTk™€¨(Š$ÿdŠQh¼vS™LÁcN†(«·;³œ(®ª€ÿ¡Æ¥Ê ¨Å£Õ¡Þ¬Î,´Ò†ÄP·‰u°X‚šJÀÙoÍâlšÄŒ+‹,'7°¦/£ 0¼2» 4ª6©&:ŠH‹5H'H‰2h± I¨1J¦1f‚lw‰{:HW€N{ˆGwƒY|Tg“eu°Pn¬T]Ï6ÿÍ(QáqÿjjÊYd€€®®ŒN€‡T˜R”‹L‰h‘œeÿÿ×oŠÿ^ÿŠŠ“’’*®©©±°(ˆK…ˆ{¦¦d¶Ç´Í8¹Å!“ÆS£ÉFÈ¥Ö±ä¼Ư(ÿ€@ÿ›jÁÁÌÌÑÖÇÑ(ÓÝ/ÑÏ4ìÃìÙ á×øÍôÉöÚüÝáí âúñù üýóñ ÿÿøù*áë3Ëè÷øKÔØj‹Š‘™™œ‡Ž‘”“¯“©—³®‚¤¤¤¹¹¸¬´³¥•œ´¸Ì‘¬Ì¸Ä˜œÝì¸Ëßǖû’έ¯Þ™©×ºÄÿ¨ÿîºÄÎдÜÂŒÈÉÈÕÕÖÊÉÕÇêôíÐÔïÛèæâÏìëëâãäáèðë÷ãâõöôéãõëñöôôþÿþõøøÑÖ勸R‹endstream endobj 29 0 obj << /Type /Annot /Border[0]/H/I/C[1 0 0] /Rect [749.0261 10.4147 756.9963 24.3624] /Subtype /Link /A << /S /GoTo /D (UNDEFINED) >> >> endobj 28 0 obj << /D [26 0 R /XYZ 36 597.056 null] >> endobj 25 0 obj << /Font << /F19 14 0 R /F20 17 0 R >> /XObject << /Im2 24 0 R >> /ProcSet [ /PDF /Text /ImageC /ImageI ] >> endobj 34 0 obj << /Length 682 /Filter /FlateDecode >> stream xÚ¥UÝOÛ0Ï_áÇDZŒ}çÏÇ¡1Ò†6¢½  ÍZ4š@I‡ößïl'm(…VBUeûîçûøÝù"™ Ÿdh˜ö– mØd‘ ,ý [ÎØøøó4“ý a pF¶Q”ASJä^Y{¸=p’KÔŽ•‚{o Ü|Ì®®›’‹ó,J‘=ÓApåd‹LjË-85Hî³ËìÇÚ"E¡ GCú7m¥{‚-†mº[¦J”¶N£æVaÌì¸Ê޾JÏÀ“ˆÜT¿‚ã‚2gZK²(«¦WùÅCA&òzyÓ¥Ìï ™·Mq]g'ÕÚ‰²ÈqlìácQKm¸E€žSü8§å¦LRÓÆÊ]-€Žá¹qÞŽAÛ`Ï©Ú6õ{$·è¥rQ9ÞÇÐ¥öOF =:[ ûÒRl§r\:Ï$Ð"ì® ©h£ µåJ(½³—îA` 7žª3”šœz¤ŽeZe5[ÃBÕ%ß.±\ Ullí•ϴϧ/ÝUϸ¼¥NCÌïšiMMG‚º™R»Õ¡ïÚtA¯òùŠö·OépS€Ï—u:,I±jšÐ£Íl;@z ìÒF¨÷H`! x›´÷|nH{ßg mìî8¤{7õÉ?u7Äe÷”h\=|J›IÛ4õ$¾Þ^ÕµéÊ-±ló¶›'ñ|u[ÌV4bÂðÞ®ǰÍ.Èi*«ý#4,Ã?j7ÇÑ€-)„R*zRþeGI8¼ ›o‰ JçOJµ*\l—ÿ»o£®gçŒ0¿‚ࢠ.>o„M/Øh*O./úÝéÚÁ$ æ¡ͬWú´|šöo½kâº7Õ „ŵ²4»hRŒ²=ì#„^pm¬ï¿l‡ñjiÜ CuÝÃî7æŽ^…oµ9<ò¡qþˆ›endstream endobj 33 0 obj << /Type /Page /Contents 34 0 R /Resources 32 0 R /MediaBox [0 0 792 612] /Parent 22 0 R /Annots [ 35 0 R ] >> endobj 31 0 obj << /Type /XObject /Subtype /Image /Width 1593 /Height 612 /BitsPerComponent 8 /ColorSpace [/Indexed /DeviceRGB 255 36 0 R] /Length 28403 /Filter /FlateDecode >> stream xÚì½\S÷½ÿ’­¶VT°ºª[ãØnÈÿ%ZubEVûxJkt÷~çœ8݃ N»ºua6€Ê?«õ/ýÎn×{E>Úi7[©Øª÷×ï°Îµë¦Ö?×^·ÚêÃN”†œsìï|>ç$9 ƒðzV „ØÃ;çuÞÿ90`;¹[þ}¾uöÞk—[8îέ½;8ÎCîb9–?•?ÞýŒôU+‹C ±ßbóœñÛ¤¯š¯Š·—Ï‘-¶sÎs6';'GøkË™—mÛwæÜ6+ù.ËsÜ9ëçô)¶ïºAŸçò‚ËâϽƒ 1ån\Ø& ˆ ô“²Jñ;{+šÝ,ïöädr¬-gFŽÑ¦ÕµZµÞlÒZ &«UoÔëõÆl£Þf̱]nÊ-<Ûã!^üdžGb‹Vñæì|ê)xn~,ÝïfYvoú£Õ¢Õé´z­E ]²A­NL~Gu_às@ Á‹'ø«âém¶fz­VgÝúµÙ¬ÕéSž¼‹1sU›çúN³Ã\f*1›·h““Õ)êo–jRŸ¶XõƒVm~H¯·æsÌÆVŽ²ÑŽìLîK@ˆyñ2í ½N¯3mÝh6›u¦Æ1£-;­×ö¶±¤[ÃÃR9ðÜz^cµXRÔÚRuJªæiƒÅ’œbR›L)zÖ:'cÏW÷éø¼(ª€h׿6Q**®‘Û{Í7ˆkpïÚyÓ˜9z½ÁdÕ–™Í–ÌÌl«ÞjÞáù4»…&ÐyËçT]æeÒŒ…‡kÛ^’ª6¨-õ¿|Ób5iS´Z“Î`Õê³mZ7oÎ'ôåÚÖã@t³³d qضJzJg+Ö}Ŷí»Ñ26{¬ànhMf­©@oœaÜxÍSµ“㾚pކ³š3 ôÇ'd~" »åü¿Œ¤C­ÖX4jƒV§6é´U›“cûŠsϽþ'Sô 2Å–=O±“»jÖŽÕ©ÇÆ ¡³–•˜÷xøœ,* áÄþ ñ ^òD<Ô£ ÃÐM:ɈëµZýX«-ÛÜ"ÜÇ»oÓþ ®»Z˜ZZé,.."1ª"ÁÏ q*òa]iA醂RG9µ@#HF¢Z­5§ZvÒ_úz[ Ì:A¯µ¥Û¬ ÉB \Ĭ·lk8çOXx¿b=œÇ*¸‚bŒÕëŒÆœt«y§G6»ÐÝ\\´®¸ÒY$F§*ÊS5š¢]ë›ÛÚš¯í¿Zî($ÓoK§Ã±Á¡Q[-©)Ëõ{¢>ì?ÏmÙ`6²­é™¶9bpŒ;çÆxC˜ø.í箽‹&1oÁΑ¾u­™ÓëcÇ&û½1[§Óm%ßöì•ò"mWåEEÅEÎBÑò¢÷ºë÷HJ½ÙñÊõëKK×9…6¬Oѧ¦ªÕ%ìÞ¹ÔÙa‹‹›K LjUg,KÏœ³`îWâ«.XŸ(KÅÊØL)Ó=;K,‡sŽÊŠGcUëõ:A7tV­1ÛlÝ'žÑ+*wˆ{948å¬,ª¬t4ûÏöâx+þü5ž'‚²NͺÔGôjµÉ\µfçÐNtv{…ºL+¼ÀŒïز'x¾jm'hBÁÝÞO—C*~ý*¤#Âzö©IêZ«SÇ8GwÎÛ~qŒ™â¹ëÅEå‚j¬«ÜÕìöl’åhU-ï!+9*×9…ë…¿Z£6•ú‡‘x®™ÌD˜fäÌŸ—͉Mç èT6Ü·o_¹}»ï‚ûl(]ò­õfÅlµ¯3O<{·^ÓëÔz.G¯ká©Ìxg6¯Û5ÊËË+i÷ë›j(=…˜ÈX_HÒäNÆ V§Z¤$k:.h̺½>gFvv+8Þ @H͸²f劼<»ðgIÁ¾òù5WÜ}ôj×ážVñ“½’X¬«¬<K1'§Yg4êô:½Ñ6V»ß#îÙà./ giˆªØ¹nCåF7‘;ÏÆtn:{l•»é—ÍENRaå(Ú°.U­1™J+wÒç)*â,ÍÞt­1g†-s®ç²Q ’mY‡y#÷í5+óòÛ©`ä-®o©ˆ¬\s;òžË‰£[Ålf¦(Ù Äd‰)i6µ˜~ù¹Z­›Hvd3÷ºyOåÇôûó3…ÛöÇ:§s}Qù®]æýôßÊ+ñÇ-åwÉmËu_RT F‘ p8´‰*…‚a˜/ܨ¥Òjµê,°ef~ÊzæIòæÁÐÅí5+ì’fP–œ´/¥ª!éÈók"ïw|Üì&‹ÀùsÒ?AÊE·0 9Ÿ+ZÅIS<·Óéá´Î 1ŽÕéÕ™ÙÉñÖN<¥?ÅP’v9wmlæÝ×9ŽQ g” ò‘@¦”ðW›ipŒŸ½xýã&}”øAI?cTcu99Ù™«¡Ð!ü••‚jäËt#•k‰L7ò×.Ê{þvx¼ÿSéÊÞÓ– œÅŒr,/õgò¾p¾×™õfj÷±$.m g¹äñLCÚøxžäשÊ0ôg”D'IçxVZä‘@ôD¡,HIU3D3Èó*¨j(èOdfff‚ùTБj²±¨ú£“Õ í~éÈ_äZµ\¦$Â×GòòVFL:î]—/>¥e´\³ETËÍÄÅ '{&A8ÑKóѩ㠌×&gfxž]=WÜÛWîÔÜ(®dDwDøÁýûÉ£Ý>‡ƒŠU%y¼£|çu%•F£Öš…W9D]RŸ2,hòÌÍ‚Çá¹ý¼=/·æ´«¡¡v•_*ª]5È—k¡pGļŽò-Ô™8—µšú©â޽œl28=žÆU­nC…˜ƒIÉZC6Qö«Ì ôášÂõ•E»¨ª(â9®LCÒß;iÔ‰z- F’%Ãñž«šÝ•¢N¡¹®ò+‹OA…à±,ÈtÃ. îç©0,_rÒÕàjð)‡ý#Wu^€p4¸IžÜ¾&¢irþr@Å+Ûv™Ä›© l¢#Và‚KŠ×oˆ?Fý ޽î$“o-4"%H¹ß½£²HÌXH¡PŠ«xò:û©{"x3‹àØo‰±,¥¤Jò,ÂsÅ›Ï_ÒH”#7ßç`4ÊsåËs\."+vûŠˆ8¡J•üwII NL}Ü0Å‹¤4Ó¿ä*SQ\Qá,.Ú%…œHÞcwyee¥˜ïVP‰ ·$• â<û‹ŠD÷BIJu§«¤(•R©räW¦€ð×ûÏË|Šš††Wu¾7¥ÑàZ˜ãhp-¢wNGÿDÄ6qöêu1@Ôr§q&zñϺs¬fo8ÉLNê{ÈžÁ i+ª,(p:ŠÜñbÌ)¾µ­¨˜ìúKTI„˜Ï¼eZQyeÑz&žI|ú1óÓ$ò áû‹Þj‹WJjS »€pÜ^)s*òsˆrˆ.‡ "§iNCêäȳÿQŽ#ùÒÏG4°¥H¼­(o5&š '¥N‚‘3O§“@Îü &‘ø m5­§ºYXä(Ô¤ÒýKbPJ¹³¨²’®ˆ%‹eÛˆ†*RY®HSßLÁ®¢"‡Æá¸JÊs³o'‰Á,¥Î¤·]0F|&†átÃî•oZCI-jeÇóïùî°¯Œ¤rðçÅ[–÷ So‘š08öRfŽV­ÝÅÿ´õæuëŠ 7hR§_‹Q*']-^YIKo¯SÕ(..ÚÍñŒ˜\ºHãÐ8éö ^€Ê‰BÅX­úÌL/y53a¸’gHÛ©8,§m§µþoÖ¸ }J¡DGh!ÝÚRb›cçÏשÕÉ£àY±'s·lp8œMªæÑ÷t1y^J=Žâu<Ïñ×vQ磲¨ØC»þ„G$0O;Jv  ÛDÇˆÄ‹ŽŒÒd›3ÁÍJ½" =»û€àÓó•¼ ì ^¯"?—hÈG‹½ysÁÿ¾–ÂX"Tùþ¼6±%PìÝS6eÌPëÔ©fêQÈÇ{Ň•’}ŽÒ‚„á ¥RÏZ”b:½˜ˆEåÎD†Q 2Q!|qô²œØ®²Txbîƒ<‹*žVT%ÌÍ2 ºÁ‰It›¾ë*,‚ýÅ«ìù±„æ8Ž,%R±ÐåËwäçÕ4ŠºQ- ]-^•×'>G9•'0œJ<‹3ÙÉÚä”ÔD¥èq°ÙÈøÂK‹ÎuGq¡Ô°ÑV*D)©níVÒÇ«6V‰ã¬¸Jš-g2<¼Ø¨îZjãPd.`Åa$¤WÉ#V¡tÞ¿°vIБo|Ц4Ä*ªüŵ.Q7—øœ{27ÏÞÊAËœ”*®Eê¯2\¨Q§&ˆ}àm|«pZ¿d©t:…Å뜩†–cãÅÏRćŠN RT\¥1'þ^[Q"½Gy̱¢a0Îcq&ЊÝxÅ7 )¥FÁеÂ0¸¹gÏ˯nX(›O•_Cõá´=ßNsãRØjñIò©ðE£/P•o¯!šÙ 9'^çÓâ)e<ËŠ¡'%£6}3Õ I“< c­/r: N‡Ã¹›6÷)ȹ^ôI¨p‰*’pŒÃD`WqÁt±| m!žF©FS/Öj)è«{8©ñ£ æíp¯3à.¹ÓQM‚HBþâFŸ÷Q-é†Ë×Tž¿ê¤è—ØŸh9;g.GÓÕ ï&×ÿ´0%Å¢+ÛËÑ”‡‚t’·•“í¯N‡ÓiƒMä‡E PUJÂA¥ƒ6ž·]/r; 4ôÅ\â¸xZ€%‡ÙDµ„6á0H#³.À@ ©ï/¡ËÕ°È'¢kA2ùKDµh\¼|¡W7¼¥¸ÄÝp–¼5ýgÍÉdÅÂ&%Çz<Ò©Ä”2õ –ùZ9¾-µ¨Èá´ ÐQXA÷iÐàNlÕ(..ª¬¬TÑI"Äw`[^d¦À!5† T‰Ã©Le:3Í›H‰rŽ50â³ýq*fÔ½‘_ír¹j½!(I8\KòŨù”öv|V«]~±_‰è¿KP ñüβ­Ò†)mæ9žU¤…kÛ"ºEEëŠ =RC9ýaFRŽÊÊõÍ*±mÃÍ]/..Ö8¥J)Âñ Q›4:ýü±Þ¢_*¼4ÙÊ™êÄmÿäB2ŸÊµH,¯ª•œ‹#ùbnœ|g•¤ ¢Tä/>Bú9j}©‘‘Ms$1tǶ5)J1=®¦çqñ¼?{¯àoPÙ>:JTb:[Eë …Ê'bf]¹³x]qQaê†ò7©¨Ä3)¤CDx%cµef‰}ŒBZG.M`çáq@ ¼Ð-»u¹NææçK•T´ w¹7!~¤Fº¯‘8ùù wƒôsø{?ž¨ÇÁH9‹s™êBƒ´$ƒêF«è‰\zœæÄ‰pÜ ú@楻÷_ç¥áT¢ˆÃ©*ŠŠŠ ×YRÕFœfe0´yTâj¹Y™ç*1îΦë=ÄÙVJè±F^ƒKsß®†šÅùù ½ÞEí’I8N~äóBˆ»QM sO/ò'Ôí‘íæH UR lvéºÔTq乘¹›ó”T9 ¨l8 6hn¨h¨‰ÌÙYêá•tÌíS<]ûD¿SZ¹Î©I-U+ÅMOÊ„¶”uå*Ú­ÁÌÉáÜFi„çþ¤•GòŠ}dˆUþ`Õi”:¹jùbo>£á¤÷/®UùÔÝ Ÿ×Úý³óì+#xžM—.)m&§C£QŠ“¤ÄbYâ(™Ä"ç:ªŕ΂«Òª¦1]AËŠ»Äã `ép\ótªÅò´Aêý#Ós×U‰­‚ cK’¶²<’¥ÂAGà„ªER:ÃUc¯ Ö Ÿn4.^¾Dt7h Jþ ‘Ì«ÄГM¥R$âìZVô8¨ÿ‘ Æ–„o:Ößð.jRH£ÓE÷ç®IS§DR%0bÓŸj]yya¹×Ë j”Àø“ë<'O•ðrÛnÏ[.øùíÄéXx2ŒrNÍn4444n”è½ËqnŽt+Š‚x2÷o滜áñÖL‰sÏégÎòRûãÝN?WV:‹âÅB[…è´(¤u€)Å :«T}Åßœë™!Õã’ß½¾B«Ž#èp9S5‹åæåû+«DIh¨ ãq4|Tí•×I{ n,^Õk—#;K¼•GJïp…Øä}¾ô:ùžR:ãK·Î"•ô…$6bƒQ:œN¯÷!ÝK]…¥âqówlÙ RžëN=*žÔT©Þ ¡*ã^a¯i¤3Òù–ýùT!œpÈ‘èpäçž\ÙëÔñ&Þ+ IèB2h„÷îÊš»…Ïœë%ƒF÷*I¨§:r‰×Ç2 )šD³Þ6¯UrD„×ã“Ä WJêcxi–:÷Ô§2™àÉçP p8ÖÐ~>™8%*Au.©±ªuu1Pµ¤šú+.Wcmõ‘#µä‹#ù½ªÈm¥a"îÜá_ǧ3âSáP—ª mtH«ô0“Œ),w8ÅQUôKg¡£ÂY¼~CÅ:£ÀRRQPRºåF Y(~­ÙÑÑ!_µÞ°Ô&«É˜=gÎeŽ—ÆP‰ãÙÉó òI²âÐ^8VxgÜ.ùÈÛ¿Q[“+.ÿë\74¸¼Ýr››Ÿ×›X•tÚ¦7£QçШÕFÚÌË®þÙ62×Y ÇQå « KRK7XžÞRQ¾{?+ žåZ›[øæMWM&ëSvfæß¥¢_®Íû”ÒÓcÿ„綬ªÑW1åj¬nlè>._¶ÜÞ«ku¶é2×B½Š96½F£1T: ê7§†ŽZÏ­” ˜È5B¤¨¸ÈYL‡–¬+¯0•®«dfg‰‡JÁ§YÙD?ÚL¦ÓÓ×ʬUfk΂Ï=ÿÈú}¹–-çY*-YtZ ÿÕØ„E6¦*_Ö'îr…†N´ÃUM&ëön‰,›™):ó3gèºÄáth-—³VAbçfey8¾ ˜ŽÅ­t®s?Îõ¥6¤ZJÖ•]åÈÎðí–fú4sæÎ¾ºÑbÑ[ö—™ËÌæô9óIØêr Õ·¶-;YQ¦>¡¯Î} »€°<ïïÞ³ÛOºzŠK Vy×@õn¡“ç®$ Ö³@­&{a7”xCX,w¹•ç[Ê‹¤=EÎòBç:MAàœ”””ïàYže½q-âLŸ»›.š,Û‰l”åÌ[Àñ’Õ`Ýä1¼7™”t ¿Â.oÝë‚Oá ãh¸kk«I‹g{0\ÈHÓÓ½;Ûjp:Õêo:Š*šÂ€uJì5²*¶¢ÀìÐhgII‰Fc)-,¯h ª‚òÐgüÄf+Ý¿±lÙd—3ß— ÷åÁ±ª ºÆíÀæ½E =ò9\®Æ# —Øíy¾yWöÝ¿zŸ-žÃ=×èéœo9ËíÍÎÑ9ÊÕ)G‘SS²‘|÷Þ^«‰£ïf“£ÜYR(èFIayIeyij‰£Ô±ÃrU|¶óÄ’à·sÜÙ9³L[6𬿙 ¨›‘5OJÌ£2µ{ŒºÂ•À1St ldcÑâü A‡yÝíä`Wg]¢bsÎ&f´³²[³uZ³ZóøãGQ‘£\ÌW°–óâ­§¤¤´ÌR²s÷Æ2Ašo˜ZÑá9ËÓ4ÌÅæÌ½Å’;¶ÏÏ™o4¥š j‹Ù4cNfæ'¢sñ‰XÖûÉܯȫ±Û‹Ñå=޼|2+ý´«{ÙEöv²‘×íuN¼çV¼ýŠvSð«oguZµÆ¡Vk4Î"G‰7¸ä‘Îð÷ZL¤âJm²ˆ5Qzêÿ¸Y,¿÷Øæù6£Á’jÒXÌec'df.ð¥X_„÷~ptŽ|ÇbªË—,:ÒR\Wcîòåíu£Ç-€¾ë~¶u‚Þ¤Ói4jM¢àr8¾oz›õXçù¼ÅÃù[öXÿ÷‰xš-&KªÕ¢×˜õ‚jd®“ŸJë‘~Êò¼Ý0 0?oEîÂ#uK8j,ÊÛÏåÂÑ£²*ö)ñöæRR•ó³Ö¬žš¢&KÅËKXOÎ-úíëÍÔ¥¸e.}‹k¢p´fÒø“DZÓMœ ¶e{I™ÆlÒ”˜™™ÙÙ­YsSœŽµ·è*,:ê*,Î]t¤VÜÊуªªÓR޼¶fÑ’ÅK©xØ{2t„g?ai˜És†ãl¬:µNÐ MŠðÁYèp–s<í d›[¼ .yÜÞ–›åÜ\[sÅ®¢Ê]S÷[·l5›M¶¹Y™Ÿ¶ÒÈ–Ç—ØxY1DÅò¬œ©áXœ»°FÐ ÑÓèy`Ýèár}T]³j‰àÇô|ZKGÒ²ŒVNg„C­Ó9Ê5%Îòý¼øMy§Uï@*¾mÿ®Â]Åë+KSª¶›Ëʬ%ûdÎieyÞû½Ž«ª?rõÔÑÝH{ä>߃6lzjo¹JµÍ¶št‰:³ jµÎlvh Š ß“5ŸJGÓ'âÏ4oi¡m9¶M¯/­Ø]QQQšút©^kµZLé9²²Iê›çs.·)?gF'%¾€Îq蔋iêt/´Ã( µ5$ÝÑccKy!u)æ›tºìA7´jði‰ESPR!èCÎ7Õšàö¸÷k8ÏŽëúäCŠÁb©°”fXR-[¬J·8J³çf_¶ÍubÆŒðÇÅxº®ƒ‰¿‹€Nx>//?¤8’´¸”wõÌÓ8-¦ÈíyùKózªr“Œù+x:£N-H‡ÙªS›Ê4ÎÂÊõ7“D†Ø¦ÑvÓã¹³­Ì`MÕ ú ‹ekj†5C³¥´´¼¼$#'sÁjyX+¼n4)/³át@Ǭ”ŠªHF{ɪšê“=p=Ä5N'½E¹y½šrèïÞãM‚f¨ÍfµNûµ ædõFñú´í†m[vVìÜ«ÕëÓ ©–dƒ&Å¢Ýi|µ¥tCiaeậ9sæüŒ¦ÂY¶cÕ฻ñŒŸéwaЉÇákâ g}û’U «?êžp4V©YµØ‰>¯zdg ÇêuÄÛ0kµZsbI i O-\¿®x½³ØYXè,,MMÕª­V‹Åb²h·lÌÐoÍH/)tj¬¶Ì̬ËÏ^ž7‡jÐj:Šãš7nõjg9ñ7aÐtG@ ]…ÝŠVÕÖ,Éo×;Þ«å±ìÜ,ŽãÏu&ÍŽM:s¢Y“j.µ¤¦V¤¤>®IM5=n2Y,eeeÿ~õúÕWmÛfdeædÏËœ173sn&G2âo£ß'·E—ãjÈÖð´á`Ht@GÂao7+$ß¾°ÑÕÍPUíªoÃÞƒYUAÒA>ì+¨†š¨‡ŽÔW™uf“ ©©V‹FmÙjµ–”nÜ¿ÿêU«mÁÜ̬Ù™ æ}ÅúJoy¾K}ñLHt@Xø+A‘Ÿo_UÛƒù¸®#ö@Ÿ£w+%õ¸¼@¯ÓgëtZA; Z5‰[™ô&«Õª7i…›QoÌÉ^µ gþ^RÃ{ϧ:,ï—ŸÎ…C¬£‘è€pÜ–©ÊÏϳçÖÔö¬.×U»8@9VD⪽œ“5C?/s¬N§×'“:£Öh5N˜0v„̬̬¬ìœy_]öð¼7.u†ßîΫ§1íˆo‚i@hÜÒÀüūա̀=mý«ð8žˆpp¬Õ˜“m4õ9ÆýØlcŽqNV&QÌÌOøùt,·`Î^úÍ7¨›qnî³Ýx•³í…‰‹4åp¡Ô?Þ‹r×"¹Ë±&2ÿ<ÖÜâÉ&‘% | ŠA>Íþ´Ið22³nS}ÉÌ¢Ó«®•Òé¹|kæên ¹›B9è€0ìx¾ýH$ÚÆr} ޼¼+ø·ñbðÉÍz>Y°z{NæA6æffÍ¥ÛûèŽr: —>FÜÏÑiÓF(d €Ht@gÜ&%Píu£«BÒH¦#úÒþÔx^DæÌ¶rþ¼íç¿"³lÝbcëáH Š“^Ê÷0¶ÛK`/…TŽ$tt@¨«z{ÞÒU.oÿ·Ìmõ»˜Ï­©õÊŒ«Æ·Žce$dcÁjzû•¸…£‰Ü¡°î¡:q£r'ýò“¬Käëëm¤Kƒý$óïÝ©›!£UHt@(žÏË«g†œüHnër\UÓÅPÕÂåùùKj$¯Ãuľœö /ŽHŠcž8ÐögsÜ¢ŽüL¼{Ë~zã)m…ä2¬ÎV”R!ôd­îÁKÝÍ Ñ]ãJÞA)ªW-¶ç-&ÓªjkÔ保ru¼õÏ¿8v U²D v¹\-Zl·çY.:eJš6Xl+û¾ ©+œ½Ü£½üìÊ‘‚D´;cÚW¹\‹hŒ‰H€ØÓ±ø#W—«péOæ/l”¤£ñd£«úùÈüÛXI<ò;|cYŽõ©Ëúd…ï¡—pA*Z…D´cͪ†#A“¦òt½Èêô¢¥ôGr½Ê!hÇâÞ×TI‚Ð:WlÅ;—Iåœ;sNÀ£¶”LJ¯Ø¬ŸD 3>t¢á*ävnã’ Ýðª\⺗,§?´¤VJT/^±s­;‹æ9ØŸe‰^Çœy¼äuÐ?7Ö}L5g.ÛNsºÏÝéHt@Wx~Q Ã‘o?é-¯r ;ïÐ÷ 1XE]¹5ÕµÕ‹róò×DúŸÈ‡Qïý|dЇKt@9 ÐåX0X=/¿Æ§ —„×oó‡Ë×2N¦]‘v÷ýþ_ððldžèB¨ŽåƒHt@ÀuöóÁ*IjW-_¶3Е»ê#ñ ZY©NýMèŽ%:: ÀåÐÅb ÊÕPc_ž¿Ðœë𺠮êå‹%§ÃU-_þg_áŽæƒŽèkdkÅ@•ËU›»\øBÚÍáËu¸N®ò%Ç—,Ï_%•Rɲ$vû•è>a£=°ðá^áw8r©4,ÌË'•R^•¨©õùÞ Hƒ ùö¢®Fÿ2ûʨ¿6ÙÑeä çŠÝ.¹´¢ÊU½Ä.O“×¹–ˆ.×¢åÞæÀÓ5´õ/÷$)Áõw‚Dbõ_ntÂUàãy»/Pår^˜ŸoÏ“9 'óI¡ªUùù5Þº*±Íœ8.—oéøšX8Ht@g¸WÚíb Ê%¸^ð6Ÿ^$•W¹–äå/ó>/#?·–t‹K›ÿbãx„ItLÇè*À`„¿0=>iúìÀÆþ¶è=Ôºzec©¯¤ª:_p>hî㤠‰«f¹/<µH”»}¥;VSÈŽ$:ƒ‘³ŠÐkŠ®Øi êˆ/ÍŸ{Z O}$Üg§õU‚<Ø¥‰$$Ý!K©×’`•=ïv쨛I tt×$ Þ)Çš¼¥«}©Š¼|©iÜåª%Z"º¢—A½—+WÖ¼‘Ÿ¿ˆL¼ºKÇÊ“‚D`ÐÃD΀kì å-൒nÔØÉb®‹6wPïÃÕhèÏÏ][º¾£‰À`¿Tez– Via¸B®ë¤äWäç6øû5¨pÔʧŒÐWbî]À2rÀ`æîSíj„¸ÐÊ!k ?â»gqƒO,ò«]þ¡¸~®ÄàAÃ2rÀ æBû5E£Û=hN.õ*Žä„®\5ù¾=OAÂõ“FÂÉ-::ƒ”[¡vÅ·ÜŸrøH–Æ j!ͤ¢Ý€®“vÿ€«<ûÊÛ1zäè NΆŒÕOñÈÛ+$1¨¥mâ2ŸBP W®ø))¸rÎõ—ãæ=ïŽÝƒ‡D`ð:PÏ0!#õü±‡¼ÆÕ ]HÊs]®?JíáËÉhCWõro”*ïJL¿¦ËÈ™K°,@ŒÂ>Z6˜ø0[ón¯”Ö9´ìéø£¿Ê^í¢Ú-n_y;Æ#þHt—âÃè†"l¬…§9òZ×Ge…;j–úÖ’ÓɆչ‹íy+® ‚£ˆ€AÃÝ´0²Á$t£w¯±“DFÀJØüš†…²\ynu£ËÕ¸(Íà¸ìÆè*À áŒ"œnt6çUŽ%'kZÃ6, 2b_²då÷`9”ØÑˆ}xîÖèp²ëô„ç^³rU@’#·Ñîí·Ó”øO¯ ¦³&€Øgv8Ù`žêblþöš•v»¿wÜÛFn'w®¼âdÇ;:1N¸Üîu ðî+‚x©XœgÏ¥~F^ÞŠ•kjPèÄ0wÃÕà2ŠîÇVøÛW®¬Yó0+–cÙ{m"-Ÿf‰ká“;ÍÂý÷|Ç!r°vàÿŠ”]"Q‰æpüóÆ A2>þìÚuªâGÂMADšq üt°aôCÈJ¦[û´øÀ‹F\@Ná%âÆ/¨HP>8/ü'±CâÍóož'wž¿ví*yX+Ž^Ty”wš6yä°8rÕ©Œ6rò´C‡ïDêɱ6:Ýc£lêú3ÜùÌoFŒ×Œ ƒîtÂ6¡¸úq²;v¼ù¦ T;Î{5dÇÞ‹-8xÑÃg›'Ç1íã*&nòæÏ"p!›„å°Ñò†™èHêÂ.FV2£PP3‚z ¶(ÃÇB4ãÍ7E½ „ÿ>ÆQ‹ØÃaÞíþwýá^…Ù°Ëa™Ùˆg8åÙÑq¶ÿÍDÍ ÇyÑÑUCòÿüæ7›šn6äÌùóŸ?VøoÿYAFÞÜ‹c´m¦ìÊž¼ç‡mnëÉK„_«ÄrØJˆŽŽøN̈é*=4#•Þksó­á/I“ߺu«ùÖúù-ñ""Ÿþáæ½Á1‹†ëiJ¦[Lë~®Ëa£ò ?»ÂÑ]3RN»ƒ#f¢ÿ]ñ¢‡ëˆÇA„ãÕï~L¤„Þu"Èã˜"|ìç…¦ÌeÊÁúºœ:0)ôõâäðã#š°f@}X3B¾+V#|ësKEÖ×׋ÇðÌeK½ÂQâÀ¦ƒA±éÇ?ãy˜ÄÀãNèk¿Éë:`ÓÄPÄÈpm\l|¸å°¨Ái3RN:Pß¡1Ý1#åü]’¥?ù½ ¿ùÍ«/¿¥}Äj”#æp?'ÉÆ²…‚o!ÈDÝñ“«È—K—æ-$_áøËËÚqˆ>f‡p ½áÛëFÜÁº.R¢}¤aDÈ0C<–Ã6Ýv «fTwtrÍDq”j™7»qŠ:Âï}áO¨Ž,]u\:ŸÔÕ}äu3^ö»¿[±Œüì²+¸Î0°íÓSëëºNý¸àËÅ‘¡2äíÆãQƒËf¤œV×´+ô‰=1¥Z)ÉÆÏŽÒó‹ oýX”’%¿÷žLêŪªv´J¾ÊOoã@Ú]êM<^×=ê'µËª‡x4,‡…‰„Nš„ÚªXŽRI²ñ¼ûޤÇséËrÄEïq‡£þø‡/ÍÏžž?ý¢T1mFÌ”žP—ÍDÿX!RQþGŠR-;B6Þ}÷¿B ‡¯šÊ¯ÂÁí·@UPn]o¨?5±³`€u­ 3ŠÁ(•ÏA¸ýS)Çýã?¼’wB ÇÚŸûÇ¥îóeϹáu „äº^ F”f3Ô\Y¥ò®–®:v¬;Âñ  _¼Šv.[ös´’÷¥0{û†¯«?®DI ̨×ÀŒbƒÛRç…l>ám¸Z¶lé²·¼:qâoï*È;kCèÆÚ3äçþîu1nKžÌÊ+8Ì÷¶€÷çˆúÞ¿ãëŽæLÚpEÀŠêaF±¦úù2o”Ê{ºw¯‘¼e?>áU‰_~ë[ÿ²%@8Ž…ŽË3%WV"ÕÑO¤4•G[³zø– ‰ÍAmFŠîöo„N™o‚E½»±R,œ’ØÿáUµt¡¯‚ê—ßþÖ·¾ñõ—”cmÈìøó2¡ Jä-Í}®«:î'wÞœê"C`¼cꙌM„E·»!ù².o_ž|éÿ૦úå·¿ý­o|ãëŸ Ç+¡„£’÷†§žs‹ÂÁ{½Že?‡pôו┽áëŽ'àZf3Üx}‹çüÉkßdÜ¥ŸÈÒ¿üõ8I=Þ™püª—2Ë–I„ëõ:VþÒq¿M;Õý‚ûp Ýž´fw*RÂhF ˜QT! ¦ZéRyóä‚–ðÿôKÄ«¿øÙûõ¯?²¹Sá¸AŸç9ª+Äœ8Ïs¬äu K~¿ØÜÛC}©(¦ÜŒ=ˆÌèh}Ä„£fíÂá+òµ{/]F´äo~‰øå/$㑇ýÁªc¿öçÄeÂqU’ çHQÖÒŸú+µ¼Òݲ÷…aA“F"öž?°Ê:¶ÙJ\ÿe Ì(zùy`”Ê·ˆC,§õ Ç;/PÝ„ãᇟöÕä{5¤Çñï>ï…®æXöSŸÁK/ðSHGßsX¦¢ª®×·|­‚ò0õ 2£ÈùÛ9`FÑ%?ñŸÃýIqIKÞóGª^ ¡*êq¤ù›9…Ãëu¼âþÛkD÷ÅçÓðW|ÒdGß29’½¾4s`Ì(f„C=ð_# [-[Ôpq—Qò8¾ñÈ# ÷ ÇocTÞNÿˆ;^e²lÅß©ñ,Ë_Y‰ë÷VžÓ<Ùw|@I® c3à]Û·t™?yíWˆáðæ8T'äÂr%G€$¸×Ð-äK}SGø¿¯DG`߇äÀ"ü†Üˆ ̨®G5{aFQŽû9¯»±F6Tê˜?Å!z_'9•oäá±ßüjm˜•Ï.µ¦?ç~Èÿ}í9ü;Žü} 1¼­è5¯† Ãà3£×"}ýQ3Šæ€•?JõœÜ ¸wÌŸâðå8áðu{gíZY‚cmðJ™Ã+)ÅsÞ6"˼û¢@ {O*OEú_'<‡ƒ=8Ìèx/ŒŽç×ÀŒ¢ ïøôà:Ù;¾Ô¯_ø…·ªê™p¼K„#„ϱ+Ä‹øÛ8Ü^é€Çч|Ö«œfc·òšØ§0(ÌhräÌfCQªeAy¬¿ÿoí /È„cZÇsÕ×#ô+]jÙÏo{VŽ>csL© —×DóÌ(M€0£h Syq,m7FJÖ8þ"ñ8|¡ªïùûCõÿ _5‡y5oþ¹H12ÐWÈcÓ§"ìn¦": 3ê=0£hRysâ­í¿ùOYQ•Ìã7r¬ íq\ï߈yòe+°Ù©o‰‹ì… b†ÁŒ¾œx¨‹_ãøo^”WU=<Ý_[:Vuµ#Gr;–=‡jܾãN_ 4 @‰¡ØƒÌŒêûÀŠê0£(ŽeagGùÇû‚ØÇA„CPŽÑ~áxEærü_ÙJŽŽ_µuÍ 4rô-ò¤æéÏ0/ùÞ°òÏe„¹»®þÀ”‘ LÜÈ—Ž‡Ú§ B ~Œr8DŠ£7ftpꤸÀoNÄðšhްÓj}úðª˜ãø U©TI'Ž…Ùè‘W:}aÑíèêȪ»gÓâŠø´³wC±Í!ú}{þŽoð¹q¾æá)ØûЍymæ¥öwÁŒ¢‹e­V’·qÈe­ã¿¦õ¸íÕã^çyo·àvtqÂzS¢’šÚ2ùòúÞ¿ãf⦣ÇnRúÇ%ÀžØGfFÊúH‡bä”ÀoÂŒ¢’ç:JQûå! Ç¡Rú…ãÕµa†Žtað Ïÿ£kÇEÄ¡CÛ3Y5̰º¼ã{ëî•S¼QÔÃÄ>r3Š€p=XôÍNÌ¡†þ \ôˆ{²þ?©‡Jw¢®ýxܵ]ÎŽwþF§>G;odˆ C½xÇË&aÇyWʇá›ô…|³±C3B¨¡Ÿ=âŸÇ·ý €Ãý9Ž@c­÷ÃÚÝ4Þ3øvÊ…àƒ6,Äòïxß]/1Œôuý¦‘ fĦ°u÷Jï§ lá‰y†®ë†Õ f¤ñR}§ª¢ oF5Ü_.FZ8^yñÿ@A8ükÇ»6ô˜Ã]4ÞÑøvÊèàƒ¢·kïxi»ÎÈSa¢Ô>áˆC~Ì¢·Kf45¼ Gx3B¨¡ß=¿ÿïY¨ê•j´ßãøÍ¯Â ‰`S8̧ ¿òàƒ&»Ž›Ú-áˆc&=~tR¸ÉDqþÁW²iJœbc“ޛѤ΄#¼]Àûº¿=â=yŽã:—ìqR©Rd›œÖB¬lƒpô«p0!ßñSOy ûŽ—”) s´>äP܃¡Æ á›ôÎŒê©/› / B ,pÑ#NÈs²=Nª'ŽÉÇã® ™¿ŽPU¿þÆ™¡*9aÞñÒöSŠPÂG•ÌÄzÇ Žž™‘²ýÜ‚. GRý}ýÙ#Þ WŽ›vÌ¿áim˜²ªœñÂcíÙãwüD_ùTûÙDG㘸ã!çãâ áiFÛ›Q pÔ‡7#„¢R8î½\Žû I82B G ,«BެY-eO“ãSÛ—OIÝãõLJ1JYü 9ŽA•ãèfrÜgF =Îq Ô•¡*Q]ømiVÕÃ*ÕSþPÕ±0±¶8‚Ö‹ª¼NÑ(«Uén¥oÞuÐþÔFy°Î±–•j¢ª*ö«ª&õèúã¥vf,áÍ¡†¨LŽË„ã7»wW”nø—oÓu«vø=ŽvÂáKyD0;Ž> Nü;Ýíã˜üŽŸâëôóêÆHFy ¾kø F›Q}wÌhJ8ǵ}5ô{ࢗÂA9~üÄ{ïxï/o>,»wm¨‡„‘´_LK˜Y²ÈqG#G”^¡˜ÒYpº~"ü&ÿダ›Ñ$ŸìX8:0#¡†þ\ô$³ð· áx×ëgøý Òâ&Çɲ*Ð}ä›Û:¸Tôµe 뤆èÆKa×EcVÕ 7£ÄÖíªªÍ¡†~\DÂãÍ+á²ãÿÞuý‰|:nó°' ïpò–ßÄÈ ðëÄ>ŽzùŒ*!?ƚخ›­¶ 4#©¨cáèØŒ<5Ü·À…‡»Âqì×í{ÿDvâmןÈ)t°ç ÃŒÃ1áxç7¯þú—ßþåË/¿üNp¼&7þ«õ8„ý‰lY´rdGó°7P*G¼tÊ_Ž{|ê2·¾®áUjbYô 0#_–[ÚŒŠ3ªŸ:‚̨±®á€ÅòrÜý€ß< ¿ —ÿŽ`¿"Cžª‹<ò¤&Ú8ƒ);3£z˜ŽWèCïxܧOÇÚ0Áª6Âþdrˆèt$Ù${þ‰8Ü0£Þ›jóbR8~áÛä4ÜßÈqì7!ãTt{,a²™ 1t$‚LDRfÔkêaF1-/Ê<އ~Ø?W].kÑÈ1€§5ã"ÿŽ?¥DRf3í…Ã_@õ¢\8yX¾ãµá9®âö+ò$ÇÁˆ¿ãåå÷*옅¿f„GlpçÝ@áÇí÷8 ǿãö+òèô¤>T!6 3‚¡ªBUª$™Ç! QnD#G¿ÂÊ;9”§êû®¦ŠAG #7£ˆ—çÁŒb[8^”„ãR¨*IVW®w-9úY9”!š·"…¼mKÉâ`ÃŒ`F ½p&ÇUÃO´Û>`µ‡pປ×lìÆ•"B ƒÇŒêûÎá€ÅžpüÊïq<,ëÃv¢‘c6E´ˆrB ƒÇŒ”}eFu0£Xäo¡*oõ8FœèÂ\õµhäèg†É¯OõÕ•"–8ÁŒ`FÀ‡_޽ø"UUÜ{]4rô3òæ­ˆ†§åµ0hÛ‚ÁŒ€Ÿã¾Â©w¼Ç·¤Ps¢ 9~uÙ®þ¥MdPí“â{Fù%tŒ›QÀ¾>2#Û1Â=Ùl\_Žãë¢p¨dÇ«a³ã»qûùÿêñ^wûÊ{°à fÔ3ŽÃŒbYãø;Á9å{ÍU‡p î,ùŠÔŒº©ÏŠQØ0#˜%ÿ%öqxgª<Žß†[Ø©pðMp˜ïãµbÜÑȧ4™i<Žòà2£1ÏÆîs‹f¤„Óü-0T%¶qHÂÁ¼×•…¯„r¶éÌ&<ôÀ·pœû”€,3"Uø§†œEšlf¤Yñx'Ì(viƒ¯ûæ™™ãxðÁ|àÎà8÷-‡܃žÍÅn _{eã0£žµ”ÂŒb•þA†¿ Žûp€püWûÕÚP;yŽûòæÙ™c <øàC>ðÐG_32ÂñéÀÈôH`˜ÌÈ…Ã߯qBîq¢*åäq¬µ]h¿{iõÌGðóИñf>óÌeç>æ³€wh¯c¿øtØ 0XÌHQ3Ú3ŠY޵_9î•\8ÂÎU—v~uiuÚC^?ãAA3É4ƒ°ú4ô5ÓB*GcDÞðHir3ªƒ`ámù{1p8®Jõ–\8䪹që‚ $—ñ ägø5ƒ2ÇýŽ2ôê-ÿƒ̨wfTlF ˜Q¬z¿ š8¢bdÇ1¢ÿ7¤ÇQúÀ ÄËxð Ö Ç}V){® MFÃà5£+G}°!P[¼ R¨Jô8”ªÍï¶›«¢•c O‰š±z¦ð7X7àqܽQ_êÉ õººã#‚ž¥00£ÔáÂŒbš{ráïq¢9ŽÍ2¤8ä¼*T3ž•ò«Cyh»ŧ™I=qz|ƒÈ4Ìfº.¿òÍ8”BUrã•ð3G¼‹™3sÛ Ç%Çý`rЛuØÑ^ÖO’™E8¬ƒÝŒFDÀŒ°¾)†…ãÕÄP•oT•²Â!HÇ3ÈqôìÈà÷ëÔîuÿ Ž/0#!ù0£˜&hÇmráð'Çiïq¼"Á!~òʳÏt r÷‰;íÞ±q]On6Ng ›J3¢fTßeí8 3lÂñ‚(ßòÎ8d¶È¿K9B&9ŠWw"ÈqÜ/¾ÖîM;òh}Wj`޾Ôî:SxÃCÃðF'%>Ó;3 P”zÁŒ”0£AÀ?=ޤ‰#bUU(#DqÕÚ ô}:´¿XÞó›zr•ˆ7|¬s!^ü5Ç_l¯#»lF 2Ÿf4…ãØ¯ƒ«ª˜i©ó°CG:ŽK8Ð÷-Ìâ-Ï {©#·£þÀˆPïwf$ 1 û”¿S§©ËfÔ‘×z`$Ìhðœgüºqì×/ Çe¾÷nȹêÁrÇÀñ9ØÉ¡Þ½ÊSÖ·oî­«;u`’2äû™Œ„f ›ÉÝѲ_u’§Ýšˆ~ãæ­¾#Ž=†yBª “'w­ž:DõÌÌñcȨõù8ÖQätŒü¬+ñ Ö:F'B(Óî£¨Ö ¾‰ìИù7™4ü:`s,ñ8 ²ïûUeU3ÛkÆ33'ŒyèAqKÇxðûÏ!eÞïʰaé´ G"\oü§BýÒÏÜG3Ñj:Ût&í!ºAcÈávÉñoÉ’ã£ß•ÏUWUµvíÙÀœ†W4¨l<ðØìKnõ~ ­'†imaŸ¯]„᪨#  שºa3Rt`F :e£éÌL߆ׇÆÊvü½ûbpÿ3ú˜|ÊaØrܵۃrþ˜yá6ÒýÆiŠî]&Në¨Y+¾ýùáªèâb|ÈÓü­ÎÌHÙMÙ@Ï_l¹·DÑ ¾Ù¡ñÌ3oµ÷8¾áU%;ÖÉx\QL¶Ì|væjžDCò3xà¡™g› ýíulÖå÷û°Íw;|®´?ƒpU4q6ôyþR„ÍÞF,©Æ-Ÿ§!œÓó.xýÁ;ï¾{Ìçqü—ã=ޤcïvM8ž9“F§ÈÖq"I3ÏÜB%ÕÀàðä®\/*'îì‰Î„ú9„«¢†öU¸âo°éþšˆ"Î<õä<øÐ˜ ©ìÙ‡ÞúÃ;D=‚BU‚$*½!ü}eËŽñã½ÏÿÀãg_ú G|@1OŽëðí7ùp<‡»¡ãgp4%…üÝO÷tõ ØÈ˜ˆ"hm“àiŒ?ó™0¥³‡Þ*-ð ‡XŽ;B&ÇB GÅ–Ù3ÆÓÒ)úaúꋜG"Ÿmó®›¼¹«u“M¡3&WEBÿòÎv׌&Æ…˜.¢ìŽèA8­?ìi„”§ ©j¨*îı›œhˆê•Ší³eŽÆCigoâ 2 ¹søÐ´É#‡QQÆ 9yÚæÏîtçwv1ô•&ÂU6dn¨‰ê]7#¥ÏŒF6<6ßѤcõLé)’P•òD˜ñ¸¯lÙ>“ˆ†˜ð¡™gšp£„Þx„·B‡;pX4¡«p™ÑwûߤÀ@æÙ®êÆ33g®Îõ~:fÌ{ÇýÂñ)«Q±eöÉÓ Tcf_Àç`¿režòàØ \BWá2O±8ãƒyæÙ® q8Àg¿Erçÿõâ¯ÖoDã1ZœES㟽ԊËÁ寜Q \eœ…›z(½eþ¡·6?5sÂCþŠ^äÁ)WEw{U… ½aæŒñc¤Ò)’_}“çîá°N³p± ÂU}ªp1 ª«"ÏÝ阅 ˆe®Š4áªp‘ÞÄ ÷1\Å‹O)ö1Äj¸U¸€A@ŸÍ®R$( &!!iøðáqqc’}4)ixbò˜¡C©[£7.ÆŽ%fá}®â9>!!tƒGRâðø¸ääGÇ OJ||LÊÐQ …R{ÂqU¸€ABÄÂUÿöä¸qOBÕUãÿmܸ§ŸP ÞŠJ¥“’¢×>:Fÿªªÿ¤CŽ=D8¬ÖäÄäd•R¥b” wøÍ¡1"¨Â *z®úá¾öµ#’’HPJŸa±fXÓÓ·nÛSµw_zºmÏS³öÚöUõCMT(”̘уÕh4*CÇMœ¨PÄÂôLÇ,\Àà¢W£ÖøCQ8ŠQIV‹Õ’‘1#}kúž=ûªÒ…³fÍß7¿Êbô £-‡Þ˜fP(Æ ḉ*\ÀࣇÕU=–?üaƒaëVkúžô=Û2fÍJ/³ÚÒgÍšõÐCJFÁ(”‚`0‰Cf=5kVF \-Øg³%IJzûíMþƒw U¸€ÁHÂUnŽK— (ÇðázýÖ­éÖ2Û6"ÖY³l6›5=)¼U(A?MtcV†•f<öýŽG‰pð_Fù¡k © ¨ÂÄ:Ý®®7nh#øÓ ƒ>-Ãj±X­zã!‚w¡P©†N ¨„:Þô}ûªöÒ²'}› eVk²ZÁ ‰?\ó§÷_{íµE÷»›„*\À`¥›ÕU‚p %þ„ cŒÆŒ KFºÑª2„…ãááT7†W'ï)«„C²ôôª}V›1Ý S9ü?zêkѬ:ƒY¸€ÁK˜p:è"øŠäädµÁðhâ!‰‰CrbÊ7 c““§tÉVÛ,ª}{(âíÖ­c )•J©$ñ+…"~èÿ÷ȉ?ØÝ'Ù4Tá1aÂUг]‡U(’Ç$&Ž1 …CŸ˜¨ä#eŒàƒ¤ ‡ UD.H…›ð”!*…¨qCGü÷Ožø½7ù¨VŽxÌ fÂlŒ°ÿÚ× " ÛféõD8lÛ¶mÛSµmë¬YÛ¶ÍJßWUµo«~Û¬Y¤c|ȇRHšAn”Ìðáo¼¡`{lœÀø'Æl\LÇt 0˜®:R8,¤BjÛ¶½zƒ ÉÛHןm+Žôô}ûöUmM¯ªÔÄ6+Ý’–˜ôR˜ëý(ÇëŒbüx"OŽ'·ÑxÄÒP… ä„ W¥…|(£,˘5+Í T0J†“¼u+ K J±']2™J!yŒ‚vu0¢ß¡RÅ'ýÏa†INž5k|Z†>9Y¥J6th´®Îb.`°*\Z8Òì—–!¨£T&'oMߺ$3ÊÒ·¦ÛöÙ˜¿'Y)B¥ô}¦`†¨†_xXÁ$fÙŒF‹•HLüð6»ªs (Ç}åT€ÁHûpURÈÇ%$¨TF£Þ¨PT×Nœ8dˆ>Mã˜ä!ChÝC&å ž…‚”èŠãÕ†ú‚×aÍØ¾Ýš^Uµ·jëÖŒ 2ñjˆïÜ÷;”¬³pƒ•v᪴°Âa5fÂñvõÄQC†dáHK„ƒ$ƒÕCP ±)P¡}dDíP©,Ö;­¤ÏcïV[†uÔ@ŽïwA8¸‹ñ¨Â€ pUè„/Ip3L\ÜÛo|ðAí_¨!j…”É òÀHKI©"ì#+;ÒÓSRh;Ç÷}üë¿v~¸F'%¾€(`P®J ]`*È£d↾ýöü¥–æ0”’dxÝ Ÿ`HÁ*Ÿ¨¡|"e_•ÈTeÌJIa”C£T8pœÇ®R„Y+7”¨ÁÄÉ|ð¾À”)2©`³²8½'%Å`°Z÷¤ ®†µªjëÖ±c\ê_}À KœUtRa:4žˆÁäÉï¿ÿg"S}záU y¿_€ òDŠÞ`MOßc«Jß#‡-e „bþ ݇¬>}£Œûï·ÿü'âqÔÖ>1ñ¥¾½:!jˆ’ñF°’“ÓÒŒiUéU6¦š5˘1dH\ÜëÞøþ¸@áø?ÿÆ‘ m:à {ûí?‰Âñ—'§É\ Æïyñ"^#99#Mo“âûˆpâ7Àº7¼¹ DŽ¡C‡W©emí|øáéÓïŸþë_§n¢uU^ÁP’+1x¥¢R%'[3¬VÛVÒhÛg›¥×?2<^¥T ÞÆ@ûŸ ” D‚¤øøÂiŸ©~ûƒ÷?ü« ï ÂñII>‡Òû9iHLÌ„c+[•Í–nÐ/ø, ˆYÞ\øØ8¢‰ºÄäŒ ½¬„<ùð{‡¿õÖá¿zsôè„©èVø3JžeTÒ¨QÏÿÃ'‡öŠ1dˆøü?ú¡ByèwS§|ï{Z8^“£€Ž˜ùo Cuúm6"D8ªHÏ· éUUÿ) Çüª}{Êölݳç?ªèî¿=‚pTÍš•˜øh"&eÔ¥‹gΞ9“ö½ßýîÃO“¦ÁïyP|þþP‘pèS¦>9À…ãõ×Èt Ï?9N¡ˆS*ŽS(ÒÒ¶ïÙGò³le ö-¨ªÚ+è„Í`Ü·ä3æï’˜8D¥þ0I«¤ããòÍÿýß»wX–ãYöì™ßmþðÃ÷ßÿôONžüè£äùªÿÁ¯„£ÿjtÛ§ÆýÂAäâÿI X1Nޏ¡ “À(žLÛ»½jß‚}‚pì©Ú÷ŸûöÙ¶íØ¶'øo/ÉgìŸ8dx¢Jõ0­«"} ÕM"­n–ãÜì¹3›Qáøà''N~4™EBÂï‰p¼>íÉ,¯û…t¡ã†ŽK:z4£P$ Q)éº`qªˆ’Q©†Žœ fô褤±†I*ÕO¾ÿÁ¨Q·î~ñÅͦ›¿üÒÍ=«Š{ã?¿ú}2®dÔ(†:ôõ7ÞÿO~íµïíEªßþêˆ\}ûÿù€1@—üA9† Â!ÈDÝõ¸×O)Ç8…J¡ Âa0$ Âqò*·nÝljºyóË/yA8âãßxÌÔ%c®FR2CG½A„ãý×^Ûüâo Xác€®!œù¹'Ç'$LúÝÉ“'wÊáæ»ß8‘TdâÄ©Sÿö·Q£Æ?Æz8îî]ŽKzhÜä>˜<åì^ÚÈ|ñÏ9«`È”«ÓúÓ_þ2y2‘ òÕë¯÷ ŽS(ú±0| *P6þüg˜tžãÇ?F„cÜäÉ‚lLò5"“IgŸ$I=Á²8YŽ)SöîûÉÿ÷ Ž?s†QüE&Sˆp/NxýI"L?Žp@_@dƒ¤·?øàýÓïS^ƒ„ªjk?üðµ×ÌÍ›$½Ís_~ùƒŒñöÛo¼‘ Ç“ßó°ûÎ1Ê·Þúë_ßÿÃ_ýGÓ~0{êw«ÿçÿô§7^OJJH`”Ç?öÿ·w7¿mœwÇg†ä %`YÒ´BНb`±‡,°ÀŠ¡%‚ÆZdÛã:¤¬á£ë»áÿ Ùµ¢XØ  íVâ–jdÅi.­»‘‹’EA&ˆ¤ëâ­(À¡)kfV;¿Q/¶ìX‰äØî÷ãJÕKdx`"ß<ó<ó?þ®¯ Ç~Ù¸åâe ÇP7[ŠEMSô7Šê†Ã–pdò¡°„ï{s!™´iÞ¹£(m‡ãÔ¹l.ujÒùɹ¹‰¢嵞þøö§C8ॠ͔æædUÔü¼œ8QÔ}©SòlF¡Ë=h›æF[æ8Lstj÷h@åþæñ>E …%£Ú¬7Fs†1;[.ÿ뻪â…#Ÿ÷†g*˸¼Ô‰PXVÒÊô†7ê‘ÆìŸd‘¹r¡P½`šŽ<%.ïkÕs9Zv÷¢]÷1BEQGG%1¦ÙZ9uïÞÜ\ñw>ß'“3FÓÓrΠáŽ6u¶ãý÷õˆGÓ±ÿf#$¾¹x"êæb;åÙÙEw‘S…jÕ^ïtìuË꬛fµZpÌ*ÝãÝt9‰\©¶$öZk$#áømÑçûðÃ3ÁÐôO ¼2¾× •Jew¼!÷ªdZÛ ˆ“‘J%ÿA³Ùé4§ãÑF]žÚ¨×åVU©´}ªìΛ–»,Ëvå¡ÀlZSdª]~lj §-ŠÑŸé—YŽCþÿÿþ×¥ã'?y|).à›³×{% å­-C¶Ãá†d±R(\p4û¢õÆÊŠeÝ­¥æf4ÍâP½e¹nF´Ñ¼i:ã’Že¥ÓŠ&‹{ÝpŒË·û#y~œpÀóvîlOÏø„ü Þ{“UUss¥’|47·´”:5=ÕœöVPÕë–Ýhœ;uýwù|7ÝQ‡ó™V«ÉLˆŒ9~˜QÔr¹²X, ºÚÛûÎ;GôÇß/’ŽÝát 8,gÏõôî ÇÜžp¼›ú|ºÙ”J(Zý Ëj4†N]ÿ}>ïUCquçɽpÈ$z&£:áX¬rx¥dúUnNysr£ª<ï-Ê•|T*ׯöÙñãÞÈ¢/jše]7Œ_ÕÔv¸ï5%—•) Y{uïž»sü o*#5×—º øýÝ5UÛ³ãλlÎv7&qáÍÌfNFj<•Šx…Âaôù­FwÃïãJea¡R9UèéÕÔîdFkµ^ø4EÙµšjû—V«år†‰Ôj–ˆ·oÜ(ÝHôÇëGŽ'Åco8ø«€Ãaôû{Â1·7…ŸõôleÂy×jÕëj@Ù5%®nÓœpŒäü7V(øZð%¥„ãHG ‡Í_9|Ëp„gÿäE›ŸF‚Á¾c2ß}ãÙc±@ ;º¨×õ]oìžWÔáaÓn·×ÖÜÏ]—Ù5Ñè?ÒY¿ÿþ“㱓ŽîG/þâ1¼à@ápw•Ú Ç¢Ž@(Æé@á°/øVÎ&Tu¦4?_Y”xH>&ÆíAÛ¶Úí©ÚH6NDåD oƒ÷MÓÔ½ñèöc4Wm6–¥åôU]X(ŒO&&4')º6dÄX<O$âý‰Ä!o<²ÛãáØm\ºxñÒ¥_</xïœUÕOfܧŷÂ!7µî[VûáX~d$ŽJ8Tï4rÙ”J"²=­¡lOr8_ÌåšÕåeç Úòòé´¢,”ÆÇ “Îï§Ç㲄WÂ'tô÷ÇwÖã™ÂaK7.ø†¼ÿâ?ýŽŽO¼å·sîÁ¯CçFFd«BÙ8ĶMó‹zþr( ¹»à¦ÏÔ.$‡v-Ãݵ$·Vûj3•O{z³ÙHDUcƒ>_Àù©žžLÿÖ¶êÖúÖÛ‘x<{ÆŸ ÒÏ ÃúÙÓ;á‰ÅÒ¹¡ìÈfË ‡ÜT¯0bÞîé§ÓÕZ&©ìšß¹UU«m¶ú¢òµž ‡¢Æ|ŸOUzz‡2ùÌs¸¤§‡ãÒSnSx:Ã0úåW"“ÉK8d\Ù¡ªR)N„ëu9SÃ2-+›•-DLsm­QÏ::f{£Ý–ù e÷b\MUGròû®¬H0TMs¿],þaòƒŠú^ê\’ÀK—.y“I8ºÛ Cn!uÃ1ç…£è„cÕ\—pØ^8¬ 'u7¶Ýio>8Þ³£º"ó¹œí `VÝp¸·µœaI±8ùñ`LQ^€pØÜ[U2ÇñÔV¼4`Ñ“?L~@W€ß/·ª*•’cv2 #5ŽF#ÇùÂaÃ8~¼§G×ÇÆZ«2þ0Íáឈ¶½²JUòc¦Ýjd«U¹QÕj=¸Ÿºwoa¡T:7¤>¯ǯ]gãÇ?vÞ½!ŸIÛdäñät\½Ê‹öaŸ<™L¿n>5d¸á˜sñðñd0()0bÑ>'¡€á„ÃðÇûzzuýÂh«%³¦™vFZ÷v•¢ŽåMó~+3↣wm­½™Œ{áH½ áøÑÎ×dÔA8à`eph¡´¸¸´$Kp½=qÿs< …"‘úª{PŸ½á×g[r£JUü¾xÒçŸn®µœEÛ²ëõááøðð[Ñh4œÉ¸SéöýÖ±·’ñNg¥þËÿ–Ë…Éç4Ãñë]ÍÆ£áÉË"pÀpÄJ{±X™˜‡#N8VWÝ^Ø2¿ao‡# 'âºÞl¶ÖV[­¶eÕë‰á¤Cnj¥3r\¬mmnë‹K8VÉ¿Ü.W& êó™á8P8,ÂO(Úäd¹,Û¦W*îNó'#KK|P«u:’Ó´Ý#™VV‡“Š’N7rÇélµZ¯·ÖÚN:v$.¶SÕ/ꦹ²2œNÿ½œÇ!·¬d#“€ÿÈ/æ7»ìG7Žý~ŽpÀ³K$4mò{ÂñߕЄczW8äŸìtZ:+áX^ÎyáhµJT,Ë\·¬Ú”ªÖ%«Ãé·Òò3ªªÏ ÇïSõ´p¼A8à…Bovú“p,È6¶e9ïOŽ•hÈ#€¦3¢hµ"]©Õ6œ¯µÛÎñܰxŸ7–U¥^ßx‹É©ä¿£ª33ÅŸÃÄøoã}ýÇ{ìÿ³„Ž­p8ÿsÃ1¿ŽuÓÞ ‡m¶Ú²}H­fK(ºgŠoÍ€ØÖC7ËËŠz·ÞÞˆÅT7–}GÂñiq’pÀ+•Žpøö’{xÓ­ù²l¨^./, ==++wö²¶e»ª3i÷<ñ\­6œ”¹ŽZ-7"k§¼x¸7¦”ÕÕÞ^EɶÜõV=½ùw> ‡ŸÃe|ùå~Ùx¶pì—ÂO`;Ý/¹á·ípLôö¬záØ[QrK*-áP%‰äéL£q9ŸË%‡»ó Î;вâ„CÕr9yÒà Ç÷ ùÒá€WL8lø‹“Ý3ƽَbA&ºeaÙN6ìöF§“Lt7¦ ­û¶é­¸’GÈå£öé)y0ît¼¯TÈÓºÿ9LŒùåN8äãý³ñät\uìÍá€'3†^¼!‡Œï„ãzQ&¸eaYeÌÑyLz{jšñzkÓönc¹O»3!k9ÙÕ0}FÂ!ß«V5múݼbâqCz»»²JnWÍL†Bõ†½5{Q¯×j##ÊÖÉMš2:ÖlþϪŒ76ÛòÀ_s¹Q½ hÃ'ÓéÜqŠõU[UcáÈsÙjd'»³ñìáØ›Ž«W |M8b~chHVVuÃ1ùaè†;{aw:w½phÞ^¸šra´Ù\½ë-ĵl{ýÏM'cŠ–L¤Ó2"ó6up0V5¯”LÿÙ¼œÌ¡ëƒÞc€ss³‹©séŒi¶Ú++Ùœ¦D£¹ SS~÷Ì&MííÍ]Îfs—GòÙ T¥æ¸»êÞÚZ—ØLådWu].ñå.;_=þÑlœ?ÿäßãê#xiÀFýgÏ:áx]×pÜê†cv(•Él˜›­U ‡Ö«M[³ãŠ¢õöæG³Ù©\.—Í~Å Çjw¯3昪ɶ¹„^1Cq] h±XÀ¯È̶o|¼<'V*©”¦´î§ý\®ÑX©[vÛŒF·mR5Åçm¦îž&[­ÊAOÞQÞª*]—ð9^Ê£Ù8h8ö¦ƒ—ì/3$ˆÅdÌ ª>­0î;^©Œ§4ms3=ì7jùF£^—‘D´oçÄ¿­ÇM†JõŽ»8×’t¸«ª,Ý=¡ƒpÀ+g`@UƒÁÛ÷––f?‘•UŽòüâb4êó~9 öÿLV»{X¬®o;4o¥Õ½Æº%[”È%¶Õn§³2ò_Úùó¦ãéáèÆƒ<=Š ÞþhiiqqæÓn8æÝp|~#›³mom•mµZî0Âi…¾5ôмOÕFÃ;LVÂaÙíMyÊü;>eüÃa8¨x¿¢ ½=Y”£œdÑ¥¥Ri²˜ ‡4uµeÚÞ#~Þ>¸‘°dÂç Oþí‰o|?è•D×e¯Üv[F&íN6œ±Èw~a†C>ßýýsñ€ƒŠÄ5åí¡bqq; “Åd2RÕÖV8º'F"ÎC ø^{ýïÜpƒîä¸SnQ=”õT›_e_„ñá€#c¿sÎ04ííSÅÉRIžO¥m`àöm]7Œ•{¶»S•·bÊ6s¹päXß{W®\¹æøùÏß|Ó{²ãBÓ=ÐÉìt²gdÆü…¸²Ýé8~ŸpüÑsó&¯8H82ý†áóɨc+Cš„cZW ãÞŠLvË ‡7êÉ…ÃÇú®¼÷K§W®]¾ò7'Ü5Vjõ_¼}r;f6+w¯^Špü‘pÀ7fªâó¥R²Ýaáúo'ÿëF¥òûTulÔmØ–»ÌVÎÙ¸w/S”'®¼ç 9þãÚµ`Pö"‘'9$à EÇí㢼Xì£Ê²þ¸ 逃 ŠŽñsÅÉòÂõB±x㆜¨j£c²NÊÚz°o³eYN8 E;qâ½+×®]½öË«ßÊ3âËM“l˜ÃiU‰Ä_‹"p´ä©¾`(èóv*•fg?ÿ\×åXoþÂÛ÷Ö¶ÖÖ ]÷ù^ ^¹|åÚ™3rkÊt¾ÿÙ€ÿXMõx:º.MSÕP$\ºí…cqvzzw8¼§9,{u+Áàå±kN8œŸóÙÖ†yç3_@Q^¨K"pÔ2ý‘ˆ¬ŠJš)Í”æç ¿_×[k›-ïˆXy:ܶ×M9*6óû£}‘ˆ®7§ï6$;‘È wAOʆeݼI8à‘qŸÕPßM•>ùt¦T*|>'­Ö}÷IŽ­çÃeÙmõ‚#špÂáo6u98–pÀ_é¨#žÐuEQ´bqb"N$õ­áåå‡í­V¶{Ú¸U¯7«nT«'“²eUÀ÷Ò]h77oø6ú½p¨ª2)áLªj|x¹á>è®´•›V–í„ãNó§ÕêàI™÷^º %p¸b1Å=c<“ñöOO§³¹Îå3Ùœ+‰È¾Uá°ý²^àÍ]øë€CG¿»®/“VÝsžÒÙ¬ÓŒL~Ä©Æh.»Ž—ö Lf()7£º›¨+Þ'Zn䥿4²GŽdÒ‰†{j“{óJ“Xš’ÍÀ×°÷ùà¯ÝÿÊçŸzendstream endobj 36 0 obj << /Length 779 /Filter /FlateDecode >> stream xÚÿü   & (*, (2'$7(10+&6-07('&9&>779*):-3K,,W6I.M+![-MI+OkGWTO3F9O!K+2gwno3n&5YLQEV)VP.NR8jgNq Lklk-Qq3gVGPJ MKÌ*PÑN âq ÚdÿjjÉ_Z€€®®ŒN€Qiœeÿÿ×oŠÿ^ÿŠŠ“’’*¥†¶—©©²²*°Ž1ˆL†ˆz§–D£©h´Ë5¶Ç£ÉF“ÆSΪô–þ¢ÿ¥ ð´ æ¥+ÿ€@ÿ›jâ¬PÁÁÌÌÑÖËÔ+ìÃùÍôÉöÚüÝç×áí ñù üýóñ ÿÿâúøù*áë3ÝÛ÷øKæÑj™˜›‹Œ””­°›š¨‘¤¤¤¹¹¸¯±° ¦˜°¸È‘¬Ì·Ä™šÚì‡Îì­Øï·Ñàɕû’ɼ¼Ð±±à›©ÿ¨ÿÞºÄÎдûÜ¡ÈÈÈÕÕÖÌÊÑÊêõëÓÔïÛèéãÏéèèáèðë÷ããõ÷õéäõëñöôôþÿþõøøýôèÑÖäãäY¬endstream endobj 35 0 obj << /Type /Annot /Border[0]/H/I/C[1 0 0] /Rect [749.0261 10.4147 756.9963 24.3624] /Subtype /Link /A << /S /GoTo /D (UNDEFINED) >> >> endobj 32 0 obj << /Font << /F19 14 0 R /F20 17 0 R >> /XObject << /Im3 31 0 R >> /ProcSet [ /PDF /Text /ImageC /ImageI ] >> endobj 40 0 obj << /Length 782 /Filter /FlateDecode >> stream xÚ¥VKoÛ0 ¾ûWèh³ª÷ã¸b]×[·5Ø¥è¡iÜ$Xb·Ž³nÿ~¤d'NšP(’"©Ÿ$sÂàlj4D{K™6äqž0*HüRIúó2áí+pDw>dmÈÑ’sI½²öôxÂqÊ¥v$gÔ{#påKrwÏÈR\'A+É+LUÞq2O¸¶Ô §:Í,¹M~¬"BÊPiÀ¾7V\Çȼã ¹*—ܶÕi©©U2ìì|œ}æž*H3x"R8Ê`çDk™#ƒÑ]zóœAˆ´¨š,çé4ãiUf÷ƒëäb°J¢¬¤Î8ÒÏ𾪹6ÔJ!ZLåû1Í×mâËwQ@:*Î…”Ô8oûNÛΞB·mäy´Òs傱/‡Ò¹ö2Ì öìj®È§ Š$½:•£Üy Ìî*šÆ¼TØ[ª˜Ò;¹ltë$Œ¡ÆCwº¶É‚ëÐv´*«ÉÊ »Îév‹£LZEúÑÞä윎åôl3Ýà5.­€iR¦ÓrTé@Q”# [¼+AhЮÒÉäá"N2áÓºˆ“ ˲DŽ–ãí tx GX({´ž×!Ð:7Ü…ØÚ¡œkÐçDÐúéÎq»ÓѸÝü¢y@ëfa\>ˆÂcU–Åc8½­©©â’! lÓª™Dõd9ÌÐgpÜKjµ0‡ë{nå†;‘{;˜sÜ‘œÜFºïu…Hý-‰žðRËŠ:ñ€‚hœM×$\ ´†¶©«Ù¬Åùãþ‹òy=EöŽ‹Mh*š~' #ê¯ãœoï‡ûK ÍwÝHÛØà3fàÞZÇpèþÁºžö^¹Jȹ‚{ÍokN½Ö"¼ Pdë€8„„]ýŽ82N,`4 -¨[\®Àç*n° ×ʲ ðaŒªãóÅíM+]®L'xÊqkôqø†–êO1Ç1¾ ;̘ƒµ²ð€ÀuÝÛíi_Ò3ªõíçÅi¸Zxs˜±âº+¿>ÆâìMùV›Ó+ïˆóßñendstream endobj 39 0 obj << /Type /Page /Contents 40 0 R /Resources 38 0 R /MediaBox [0 0 792 612] /Parent 22 0 R /Annots [ 41 0 R ] >> endobj 37 0 obj << /Type /XObject /Subtype /Image /Width 1593 /Height 612 /BitsPerComponent 8 /ColorSpace [/Indexed /DeviceRGB 255 42 0 R] /Length 41716 /Filter /FlateDecode >> stream xÚì½{PSçþÿ›h»§­¤Îüþ¨»FvwHˆ ßl«N=ê —3Óïô¨»^ŽÝ”¶vH”¦ÒÝRÂ%PíVŽöçœ;cÕ¯µ*¨8ßsÜ󣺽S*ZGê¥J @MV‚g=ϳÖÊJ²HŸW[ ¹Ú•g=ïõ¹»ÝÀdâq:G—Ó޳³¸h»±°°Ð˜—Wˆ0í(îtÂD, GñöBc¡‘F^C!‹Ñh,*v€åøÐŨ+˜‚&Á/……yïƒÝðt12±E &3U` Gsná(€ÈFNs[SmN¡W*L¹T¾P8L¹­ ¦Â"Àí(2óÚ(ŠjÏ÷*E-Õà#ù•ËÜVÀ ÇãÜT&SAÕJ3³baÊk§j}‚­T{z´Âä3™Îí¬<˜ ÚåheýS¦Æþ0úX­ÅÈ sßv0:f,ÎFo*U.ÕÚJÕò!V>:n$1V*‡ôK19€™‰£ˆ·(Œ(ŠÑʨ19L D'xáÈkgî¨3±*³Rsf¤nlnÏ!áÈ5±±ñVAtÜdªCªâ½£”`æÑéµ'È Æ¨h=G„#<š½šÒ€tƒ} ³½  ÀŒÓ £OÚ”)¯YتÀn+ª=‹›7#ÝhmVv@ˆ`¦é†Ñljà„ÛVÇ`¤"‡BR£ã¦ÂÜV¬T­‰·OLùFP€™eolËm.0 ¤ÃdÆbýS$¤AâyÍѶArnS~áöpf c¡ÉTÛš#4:p£µ û§°o [yMÄMEãU¦¼¬)!˜18·³ðæ>Îaª%†’o%Ññ:ÖÞ Ì^Ó¤énkÜEä3„X¶å2¢àílØNqfjL…SyÛPÝŽ}𩸌¹Á˜C% ÀÌ ˜˜¦ÂZŠ¢šóÙHñIQ­&T&Žoç¶³·ø +3Ò>/·&À ÀÁ; þŘT‰t°9·ŒÉAbãÈmEL¾.ДWG!­áC#Û!Ì }”WõÎq·ãûœwß|÷]óMd&020øJ¼Ýpè—;÷ŽZ¿þm,ƒwëë}õö¡CGëï²Âq—äTÝ#zQ‡ËݬÃÙ¹7Yá8³·N€pļpxzq.nýïï¾ùŸ8‰öÝï³ÃÁõXjï}k7Žbã¢îÐÛ$?÷ÐÛ_Õ³¦ÖŠõGñ/ä5îý6Ô«¿¿y9úHÚï@ì[xK¯ÿ«·þù¬úâR¿:¶à¯np𗻇€‡Þ®c³­}…î»wgðν·×¯_·öMFzºnâ·ÊaD%äžÀŸÙááˆ}á8NJßõ Ç_oâÔª¯×¯Çú@Œˆïïù4:ôÚuø9Gï Þ[ÿöZÆÐøÇ6ð¾K¢ã}ø3ŽÓ ±/4[˽éÝœçáøÏÑÆó/ĸXèh}ýÑC÷ï~M$äëúº£‡H(ƒM¨"ârïΡ5Ø=…’z»ºÈ›Ý¼Ùå$Ž{Ý ±/}¬p|ò©XåÀ&Ã÷o®]‹ß½Kªÿî%†Æw(®Abo× "âëëŽâ°FN®éú¿U'*ìÝȘ5ÇOx@8$ ½D7:pî "掮®ï×bá8z‡MÀ½'ÔŠAbdÔù˜o¯[·v-£lqträ Ž}n€ØO ?¸‰p“#ÇáèjÀð |£"G‰Œ óã(ûX=בљ¾ßûP ºÃñ.±?n8{IøÝ  ‹ãnŒÛ‹êÿ7P`bíšu (óöIªäªýØr;¿üÆ{§Þþš-û»ó_lgõCwpEàà`_Ÿ#—Ž;p ÇñG ’\þ÷Î%R9›7¬_ÿÕ¡:´û“±±÷8á QC\8±8ê¸_٘ǡ»¤„IÇ`ýºu›Þ|óÆïQ„ƒ¤â‚pļp qó1}Cƒ¿Ü!fF=ÒXO„â?4èã¸"1Ž;ƒw­÷©ÇRso=¢÷æ»Øàð€pHB8zÙ‡ÛÁ}}ëÐ(§Chûÿ Å×¼`cÃQ¶3ÕÝC¼ó5«ë ÛU­_‡Š¿_‹„ãW7€$„£Ã+l íšu‡¼M§x! ÑpN¸tÜ»wÑt§;G½e÷¸º}¿;p¤=çÍw™hpƒpHC8Nð®*TóG²qÿÍØƒ X8êyá¨ó)ùÃÆWu¤7n)»Íºüe¨Ïs´þMÞ Çï væx/¶ØûÍ›¨åÈ»k×®Ù°Á³¨óZw8¥ê޽ÆÍï:ôö† k¾wðc=~$uè›ÞñFÆA8b\8ØØøæã½Ž®ïI»ªÿÄuã¤|ïM~â8WÿwèÞ/wî}Íײ Uë™û™kp[C–›¬ó×Í›pHD8ú6²Ê±™or˜s“4AlŒg:ú¶u(Ÿj𻯱õ¿:„zã~ï$H,®IUÄ„£¯£~ó‰úŽÞÞ¾!¾M€©?q|óæM¬n¼Ûy“‹O¬ÝÐ Ž»tã—Á;÷HzÕwHC7kýúÍÂqÂIá O¼%dÍÆ'êûàK`º¡Å¯dé€èöð?¿èþy€v÷=>°‡<ļ½ƒËïíœop¯pE‹pÔc?UGßæÍï°ºe£Ë‘³vÝ:¾o:ë¬:ô«¾:ĆÍYÝ õålŽnÇMæèêbÍ–Í}‘Ž¡5o‰°¦-ÓÊ€ÛÙÉÏö·þ‹äçO×ð³Vér».²² †Lƒ!;;3ÓpàÚ—_è¯ÑX?èóú[Xdjö߯ÊñcöuüòëŽh<Œcã¯î¾ãH8þóÍïotÝp 1ÂÃ8¾ó™!~‡kIµáPÝ]a9G™D>X‡^´þ¿È¬qF8Ht¼ÞIáxç-qNÀ²`Í ÷Å’[Ù™ü³z/ùý@y¿“¦¹ éÚùêòåµ&U­Vj´jµžùW¯Ñk4šÔÌT!Õ`¸ò ÓI{˜·sº"„> {z]óÞ­ûÑ;ø6Š]÷}ŸóýÌf߇ºárõჃ¼nÜ!ÕàõßÕ÷ݽ_üDå¿à?CD8H–Ö»¿GR8zßz ”€hd˜ü¸°oö®û¬¥áfdƒ>`øgª^§fÐiuêdµzžF©œýÜÂç”óÔŒl(çk šÔTCªfyêòåËú\ıåñ‘¦(Ž>®üý¿9Ð^?ä#¿ þw}Ÿ7ˆ« ÙìÞw$…ccPáxë¬[¦ï/y0–†AÍ„Z©Ö¾¦ûL§­dÄ"E§K¶éÔó’“5Ëæ©SS5šêùó£Ã:ßnX¾‡{òÃ5ÂAbãÄ—äD›ýˆR„'ƒÂ§ þhþwÉÓz×QáøKpáè€u Àô@×ôãŸ#Y«#ó1ÞïU»×R‘jhõj­’Ñ MÅ¢Ò-L±TX*l¥¶W’“““•ɳS•šd 2I I]n0df.ð|¹—Æos_7R%z:£þÂÁnôù«Ä`ØVÇoŽÇCœ«ê—;õüóïÝŽ5Á…c´7õtœ8q¼¾wÈ‹€ˆâ¼U1€£Ê­—ñ™ †éîK«óa¹™5z­R­NN)·èt*­ZŸÂ˜ ª•³•Öò””ädô#óÔ³““æ)çiçëÕ™½ú`%«K#ØÞ˜V›Ã»uãØ8—õä¸qcÐk<˜¬ â6I_WW—cˆ}]ÝV7þ팬p¬ .ÇC¿²ƒ{éšÍ'z‡`¥Q›ƒlðI8ÛÕ¯-5ú/´j­–±8’_³½¢™§×~á¢{j´••zm¥V[£›7+9E©L¶U¤ètzMr²Z™Ê¼H£ÑômªÓMÓ4;y”¸ªèÍ|lûªØrptuݸùûÐÐÐàУëq€Áq'ˆ¯ ½î†cˆL–]¿~Ψrx"+'Þgt¼Þ/wsG/,u&€`wããø§ë3µA£Ö(µ_T#ÕЦ.nž®Fß}À…K5ðŸ(ÏŠyê%•^¯S*5º”ä?§¤¤èôó’uJ­:™y¹>óµ/£t\¬D‚üªiR7Î_©w9Ø„(R¾‡+:a†Çá_Ú7Èë8TwwÈᎬp Ž¥¢ÙX!,À¸mŒþ"åÝXFnßF¦Ý}I3/Sƒc_0š± =SŸª×îq=LŒ+5<º[X]²Óñöèrì¶$+ÕJM™bIÖéµ¥Z­C™º©™ËEê8FÊ£D8üpöý2Ôu×ßD5BÔèì#‚ãð&ó:œŽà&ÇšYUAb#k@:5•Õ¸boÄŠ·tººl€ùòö@jæüTµYj‹&uyjuë‹ýn÷À‚óøeý¯iðÏéDÜ.§«ga²R£LV¦èTŒj¨•ȵÅ(‡Áðêc·“«ß¸-u~v—ãŽÃW6qGUÜ+òê.«6ƒCGøŸ.õA„#¤ç©/¨+ÆG?»“»ȯý®žþ×R5ØÚÐêçWÛ¾P븄H„-×ù‘ß]·RRÔ:­R“’2;Y§V"“C­U¿úêÏ´Ï £P8ÜŽ!?{ãæ xRî Ðêä…}9.>Ç’›“ îÞã¢É¸ž1{ª0ë NÀ8UØâ@½EPüÁñØ S3ŠQ­Séªkt¶Ê~—Ûãòm`å"Þ­‘öjÝCãÇ™—wklZ­J©T¦(åAªŠÊ‹ƒ¼.J„C„}Óulsªõ‡Ö®};ëÖ­Ç­F˜×ú¿Á;‚w~ÔÄþ<½½õ'ŽoÜÄ; 6õÏLŠs·.œîD²zpùÙÕt{R5êTµN§WéʪÊKS´¯1 ±¿‡ìû·ÿIúZ­ÌGôî ¢32ðÏ}ÌÛí±ªTªJmŠR¥Òk5j­fžzq¥|̈íbÔ ÇæÍ~?/ë·aD…Ì?䯛|t)bÂ!àÑP_oGߨVCpWÕ[›à”`4 «ÉϽ5øÇpù¼¥wï­FæíÒitŒ• Õèm6‹¥4E©Õ¢V…®š d–¸ne‘öUÝV¨Ø³Œ˜ÙYø÷Š Úíd^¨Ò*ÕJ¥%•2ïÇ~jÿ?»£X86ùîû¸‘úz’Y»&@/Öó&cš¬_ÏÝò×w‚|ÐTÿ/{Þ‚V%LÀ+ÅZý5¤ù ‡÷UÐ|¡_Žò ôZ½joUµV­ÒÜÂ/ˆOxˆwË{×û 2[<ÖªR›ŠÑ Uc»h´­Ö áž<ýAŽ@Õæ€qN^GÕúuk…^+n*Ç¡u^„yÂÚõDdÖøÑ"¡ê?@8E6øŸ47:Ãíqº=ÌOCffêüTÍ|æÕê}5ÕÚÊÛNþù.|ËIBüÖÿ«¿ งkøjÕªDYU*•N­ÑèõêW ·Ñs£c á@¼#ð5­ÅVÄú5ä®CœM±Ä3ÞÞÀ˜Ä Áj±v6B„Ϊµï¼ƒ”Ã_<˜;ß™úï}¸ª ®²Ã%€8™ÿºñžƒ¶nÏyÝ‚ùZF­Õy°êÿÑj++{lÖ'½ô%´\òÊc¨¼¦;€ò©ø·¼¤³‘ºŽ mÞB­V­D‘ñTµVûê«×]nz_ õ£N; M…cÕÚJ՘Цæ•_ÇÞÈ5åµã;)ª?%¿‰¢Z©:îõ…FGT­u`,—›»Ù’;ÃÿÄ&B5‰tÓ5ÕÐÏ «ò¼!U“ŠÒo5Y]¥¶º²ÒåvÚªÉÓö\"†F…¦GKeDW©sÒåÕU*[µ­²re¥Ñ Ï,,´#Ó¹_UÁh†–½F¯ÖpãDº?ŽÕŽB#Ù÷© Î嘈´á šL'Ø[ù&S.{“ˆ…ÉØÀˆeæ„£8ºþÇ<½¢FÇšßàü@—Sè¨r¹8ÃaÀƒ‚ý•*ƒűõÚÔLÃbme·›¦½ÍQØÅ9\Ö½V–òª}Öª}•*U…n¡N©\¢µDµäÜç•ë,*µ:•±:4zÆàÈ|ý¶‹³ïú¦|Fsƒh€©Ž38r¶å³7 Œ…ymøö9ÖÊ0™ò›)ª)¼fGôý¿ õžØ¼nŒ€:6mÚ|¢ƒ†sf–½áû“m‚á¦P%†Ûý%Î}Ò¨´ŸU¢æSNo?)”HåÂ/qºÊÁ(Åÿ2”Z+«**[EEŠM™¢e­–™ê”µ £¼^½!“ކúð¸-޼Zª-×´5r9ݨ-4ãƒj2òzB\UÄèÈ%2b,,rFéÿßïŒz¬ »åH÷Ìw: R¤Ë»cq¥y=ݤ]¡y€lÞééxƒÙ»›ùCS­ÖiÔÉêùêÙjµÖ0ÿ3:3}kŒÍ‚ë<\ “ÿ¹¿§´º¼”ÑŠR óC¥B q™ÿᨬ´TXT*ÊÆX«ù£è]… 5ÑêÔÚņì,§!#f`'‰iÔåñrßÎêF{û î)“™Gz¦‘}n3eFÊq#ªÿ‡:Nlb¨m6G‡OßöHÀ‰ÚlI^5רÞ- WV:± Ò3ˆXKÝ«5óÐH&Æ.P髵•ÿ¯Ë™…‡¶YV26ÉÕ~]JYÙÞÒÒ2F/,¥eŒÕÁÜ,-µTh+õPiµj•Ê¢T©þlÑa¥±U 8‡ËV¡#(4žqÙ}>#vœÅF“¹‰U06‚SÕŒ´„˜T.ºYÀ…; ½Ï6æ¶;%±˜ê÷Â("Ã×…>*šo"Es>ªaÖ¥äB½@æk4óѽ!sñvK9<ø#tOiŠÍZj-³–Y‰w û©Ðe6‹­‚±3,ZU…*Å¢ž¢ÔêRt5ÞO«©@o®QR Ì;§ãQŽbcNW ›¹ä)#ΞB~+ŠˆɼjÏ+ô!?§Pºá銵æxœc M³Ãí3äÛ¯~.Ü.½j±F=?u~ê¼ÔÔÔWõÚ ç"Î~ßËy§Ê«t*•u_y¿k¤¿û`O•ªT¥KQÚ,ŒµQaQ)u³u)É*Ýmt§^òTÛtêT½fy¦^ŸnÈ"Î1÷5W¬HG±@Lf ›í¬]aÂÑñsÄ‘…4…jæ3xÙWHE7‚Ô›o„hHR7X2ÍÄØx8LîÏbêîwë4©óçÏ›?_½85“1>ÃVÈÖ*¹h©"V†éF•µ†Áõ»µkN¿eïíŠr›­Œy´¢ÂRŽ:á*••ôôŸðvcµöÛ,Ú”äTõòÊ/2² ɧfÿ-f"äBåh 1 #'$yÈþh&¿ÔâZ?áŠn¸ V4S<@‚\±qpÒ“vuI‡ÍBƒ4hK¥Wjsc6#êÔL­þK²³—ï=@­,Ä9µ×jÝ[Úï5ª÷“Ÿ—ºi7”2F:TåªY:¥R«­ô83 Ø%åÚmSV«5šÅ™‹ YóÝ-¦”ƒ±*(ªÎ×uÅ%RÕ¡š¿Z_ÙŒn„7S<@‚&{iÿ˜w:÷mšþR©Ä¡ëÙZC¶ú<ç ¢=¨9º»Ç²—$ÞîÝ×ïä½Kl_+¹ÍÜm-c”£œùO…Z¨“¢qRGîêÖU*uõò嫳3ýœf1C§Ñ(Ð ”|kä§ê(çâíBƒÃXäÎ: >Åò«`2ð8ŽN ‡sÒrQè w²e¸%ÍWQg;{ÏÓÎF=ÕË«hZøh9Q*Û£Jøb2vÃM——¡0¹ÅRªRi”*¥Žh ¯{®V¨Ð¬ñņ噙nß^ˆ±ƒ£ˆÕ $faÞTEq‰T(­ŠjÍøªv8%´ˆ7UŽÈ®€ãì,.ÚN®? ðvb,ÚQÜ9Y;J*¸]ÃäÆöÊ×L²¬\—XŸ“ËÕ¯žŸªÖ«5©«ç«¿¤I÷÷õl´x°‹ÊZZfÙWíÄuÕ5$âÝ]¦Ý5Ø]åêG5€¥V‹µ¢¬B©Òjm{‰Ëjuë,ªhÌørCz–ëz*šš²þ˜û‹ñWhj ¨s¼)TTÞÆÞLÊäJ.Gð1ÇA8 ’–†£x{!ïæÈ#¾pÖ‰QìˆüéF»­5d4”A§§Èd'`dd“?ÅŠ½¥TªçÏcÌy©é³8Ý#(âͰ½l·ÅRf--·VíÛ§=Hþ®2)ûÐU‘Ö·¬PÉq2¹â ¹ÅbÑü!N!“É 2ó¯"޹­OÕ/6 éé?Ò®lÎyFÇžÕ”UÕd G^ÕÌÝ.¬eÌ*×db¾sc¡”ÜT˜ “7Ù‘ÛjÕðÊFaaÞr¼:²£8âv}±½§Ë}ý+°›×€œÙÂer¹“í4åÞãrÇáûþ IUk”é™Õh+`'t¬ÀÉ÷•î«îw#CýªÀwÊè'b‰åÀ¼3s§ÜZ¾PÇ>ÌzÅ2X ˜{dj­f~ºöKú IÂã¾À<&{B^CÞƒv¹eœ0"þQ$žg£&.ú F“d2KrŠ=KŸÀŠsCžžžžù¡:ã9‹¦‚6ßoÑÔ@šŒpž«Z ÏÙ^,IïÍïÇE…£ÎvˆÈÅYgQnm{SmŽQ°©4œN‘K¼®0‚ ßø‘„±ûud¯þ¹ŸXh‚18¬»‰ª` %¨ggeª].—9ã1VªRÕm«•<=y‡ƒñž‰e)‚‚Uæz¾¥j¶Cä •R­•c}‘£‡±„(ÐÓŸ[ýÐÅ9Íb]:¶45‚ãFSU –›ŒEÅN©.lº÷øšáøNxˆ7vù ç(ŠjÊ7ñžZJ¸åš (t±jŒ˜ÕQUƒÍóf¬ ¯¥°ñLôkÞíã†]îrì•ò ßÓl&)ŒëqúeütUY¹Õº nwµ ϨáÍ ì—Âú —!µRíß+'’¢JVõÄa­àž©À¶‰Læq¹ßH—Æfê,.2 }U[òÛ„¿2·‹:¥½¸=½Ç!6‘ß[vkÏ‚&ª…¢òù0j;UkZ\g£Hù5ÈÛx®ûú«F~B¿Î&[ùC’?ËpoïŠ?¨S»…n.º§´ÌZZšB”Cæñ0oê<¸×ŠýN Ö …•Y$È´9hÅ·åO¤hi·Œ…ÈÙ(qZIjßt;Š‹„¡*¾ÐÈPÔéœ+üQÇqÐ ˆ(|µò‡{û‘ÑnôËä¤òñ=Û#btˆ¥*yïBÎ$fw“ÐÇm-…X¦· Ÿ>Rm-¯.-·î“Ëåhç÷¸]ôþ*ë^+—+âØÀú=!Îí:hµ¢ÛŒ¦ ¢p»±ø¹¬Gj£pŽ5V Æ´Ä"b,Ü^T<#TƒÕÏŽ¿À´Yˆ¬¹ÁE5Z½Ý‹P©X«;<—/:ŽèhQ\ñGÊÄéž²• ôÐ $% ßí\®Ó"e`´AV‰6õ/nÓØ±îµXJK-VgQŠ§ËŠfýíU¢ÔÚ8Fâä¬Åa™µÊj-—%Èfé.»»ÓL”‘Gt:ýg†ìÒbÞÅ*É­óFggqñDqqç ÇŒ»ðîë­?íF 8Š|lŠV¾é6J¿imÍ5y•Èì‘:.ï*²Eƻ٭ºÌB~ª´$‰J†"Ú®lƒzö¼bÌFvˆN‰ó©î—Z-¥ª< ‡¿eòëÞ½Vô ”¸Øa±{«d‰$OKaÙgµZTK£;žtG"ëÊRkµúùYϱ¹¹¯Á¦FŸ$F¼V… értÝÁÖFv¼¨ç’ÐÁ?ÄÙä¦/§4J%«¸æíÕ_Vf-«HQ-Ä¿‹" ‡etc/ž-ÛƒaØOgÞï«Å¢*­!íÙi7‰†ÇÉõúù(JNr±b²ÍÀTÐ)싊ÀµcáØÆ–·òEƸA7z,Ç› »}RŒÒ.\¥‡„cµA©Tªpx›1@hRèvöWàfS)ªÛ(Zâ\JÒimÖ}ÈÞ(GåÝø&ÂEªþd²„¸W,•û …~ˆ³~ž@÷'Äi YéNÚMsÙÀJl~§žkI‰II—!<~~…C!tÃä[T\˜×Žºa3»­ZÛóH1 ± ™¸±ØºãÈU 7él¶“yI}øšA©Vª´¬Åà¾|€H[ô€u6iBrõ>Ä•~n—® Šy¶kù ,1©#‰2ž¬ñ±Ò+¨ÜX@#ÛêZs …MF°X´æq©It<¯‰èFë¿xG•)¯iŠq{„ Ðn++ 4ÍçÉ•š…)U2I‹"ûy…µÔRVŠ&jìGò"Ç{=)Æx‚$RÅá¼Ú¸n\$H»÷Y+–’ñç¸p‡Ü¦RÙâHV•¿«‹«|«_êJ™€ó°ÈÇŠï ¿€t€/Îí¤:¼UhtÔb…Àð¼6lc4šLµD6ÐD ®TÐÜÄÆÍwDÔž§³²èx9 ‚;‘ ½^–¬ÑiuÈv¯ÀY¶6+68ÐPqɦB÷bKEG²qI¨\ޝºGzÐô Q…«l;…̦ÒjÙΆr2ý/™­ɸ›_ê°\(Ë`•OÀà Ø‡à €Ï#?>Ôkt4ñ‘ âµjmmËÛ–Kná ¹‘ÔqŽ“Gô¯••N @æv¹\¤/ºböŸõÊ[(UYÃ÷PŠÕj)µ–îe„£:NŽïG/¾Fb$—4a—ÝG^ª}Vdœ°Áæ‰qØ!¥ÐîVkqˆƒô?d„CC첮،px.uC–Ë|ŒˆLð8‘à)6z ª™sA±ÂÑZ`2qrQ`lgoµå™pò®ÉÜŽÇÿ°oÙyŒZÍ›¦‡Ýܰ Û”K 9Ü#5ÄܰZˬ¥#ÂîR¤IIŠ÷Ç‘VUNºg¯µLe±Xl\0„¸ª˜©ÔêÕó½Q 7.ëÀe„ÎØ pÕö,‰°ÌÇÈ ‘ìk Á XÆBa**—´Sof5¢Îd±ñÖsT®™Ä=¸º@S^þã-ðˆp˜#‘ÝÞ]Îù)ÉlGt¥‹Èzhõžì§BSbK-•rìÌRĹI„"ŽK³uW•¬ÆZÎF‚Œ \ׇÜîban-)ík0šL¹­¬r4#Cßjjâ­dn°áó\A «ÈVsÈ‘ !O —ÛÊ*RHoB6“Šô¯M°¢Â?$¥ÿ¨PÝ–q±qºÆæ¢‰ jòÚ°sýlÖªRKŠMEü_rù¿ëʪØÁY™ng*™ÛyyØÃ~†µ™ŸÓnEÎ’÷$bÝÉ ºR•J…Š8’ó$ǿ̶––•á„Û}¥–œ¹+—?ç&Í×É@¦fǤãÈ ¨WRRt¯%ËÙ™2Yz­‚ 4§ñç‘Ð ™Aë–“¨Jl~­Iਗ਼(õA„ã8(6út¨ÊeËû¨†ílH\Œ¼mÄ6ñk<ÙøxÉ™ýŸqq2¹‚ŸŽM2ÉOÆécþ³”ßFª 6Øûðƒ¨»¡Œ›þ‡^À6a„¤¬ªª´Š¼±¿éä#8{‡$rÅä·zÅW7d`¡ýÂåDå€(0ãqàÜ(oÃto¡FSnSÙ š sÚ¸§5øêFLŽkYìO9él(GÅĹ„~¿þš‹?ÈøœlUZU#cÓ H‡OW$Èö–ZØ©X=Äw#“+­Éj=«'ôý,×rsjÿ¾r.e7ƒK}uc),ôqG0›R«08PŸ©ÜÜÜ|#7ë—‡ö ÂÑTKñòâÛŠ½0Ï.éèÛ,&PCÌtœÛ m(¥–jgKÿL9œ‡*¸§ŠÈ;•ÜÛµªh©lò#A®`ƒrn×Ç!4žœŒ‚•‘1¨H£´R®àå…t¸"ú H±XØ›øý8ŸUJÊl­Î=Àš(h›HLàZ]¹q 6YbÒâXæ«I°ÐÇKï_…:­3Þà@½§(«8×€•ÃTXB1üÄßQeªmÍ‹D”ÃEÓ\.”ÀTÀCd4Í Û¾ ²ÒWP$„-î±p½rÙ8 zÉtÉJuzúÍeRáí–zZ%x‰1øWa¡O@:6ùéÆÿù;`†ÛâEÍØÜ`»‹ä™ˆ³*\å šòL®M ª1§ò&RËq=koÚ×?pÅ)¼óÁ½™PrYB¢ÛýžüJBD,22w‰-þf]YXi,{P­FœŒ‹“ÈdÉóÔ:CFzzúu71FPdæ<;ªœý»àØ|\B ¶Æ]!ƒªñÒ±²ª@@gnmn~¾¹–°ÑnFQr¶ÃÈèêAQùÛ Ì¹ ¹æ<`/¨£¨ö¼Â ”˜q ‡;#ã'ØH¶B!HžB[9cÄñ± .ªQ…t'é& æ„̦ÿyÜ,³ˆs±­rQ®\ÿLÜn‹R¯OMÏÈrÍ¿‰òVW‡Ôù? dƒnÇb„÷½¡Lvú¯¯„Qò5`pà©*܆L3Û˜ŠªÍg€ë62º£ª ö\+±Xškëêõr®nËD2r=NŽó Ð/ËR_{BØJ[œn÷áêØ\¥¸GU™¥”´ª*e;äÚJ­å¨•ÎRm©´í¾ÝÆËv÷»P–ŒçñðmíR¥F¯]ž™uÝíñ•Úƒz•Ð1?b|¥¯n$@úè„:Îë„Æ%µ Q´³6Õœ›_Xž§ª­ŽµTZ‘|AeñU±Û6þáJMU[TJ¥E¥R¦ô ¯þéÔ·Ô¢"íF¬X9Pê²Ò U¥MóJuuÕþƒü°pšy»áþwÿEmZ[ýª:3=ý6é×=½%»¿þëß*ÝF"ϯÇßB«ã`o3_ûgÊ?Ç5L§¨öÚ¶0c”h´Ü8±}ïáu7ž4îÊzU£R«´«E£ªt¹³G\H †Ï›Ý.÷5DG,X2ˆÉaµV–UU§ØÊö12SSéªñcF&Ò¶R§¿U©? Õ²vue|€?nÀvÉ…¥%wK¡²cý[õkoÃÿ"DGǯ  lSej ¼r~Z•_†.U‹ËA&4D–NO'[ÝêtÆà°(VZÓÂ2ðs†÷ÉÈÈ`·°mqK­–Ò2FZÊÊ-¶ŠŠŠme™¾Û»uýøm²2V3¶Ä­]ªî ^[¹xñ«YÐý׈ÁQSC™ºN¼dcÝŒôí§.[ 뀲CP½gl\ÄéM.éY5±N®aV@ôU©E­DqŒÆâðàT4³ã{è*n‡µ¬ª´´Be±¤¨Tªêª=Ìó\|TÝp¹içßuÚÝ6­M["è1-Å/Ú0™×¦‚þ†FS~+5~Õhmon®ÍåÏŽ#ÈÁíá~»weêSKKUÊ…ŒqaQ•ùlòt7[nѪ[ÄRZYY©Ré*-UÕ~Z€[ôåÕ†êý5ÕŸië³3W£â>ömذƈ”¾U¿~ê+`A¾µ{¹ãSŠj«ËÍï&0Ïi%ÙË]Ý80î¸à^iP[ª”É*F8T•Õx£? ×’ˆw¿ÖRUZYfQ©*+«*«ª*S*-6ËÔ‘Mþw»Ý² šêêÊÅ‹µzmvz6¶62²ñÇÝÌÈÆ2µŸt¾Ô«ÐÞ€I¤“k0edä­ãˆnPm¹y&¿úñ1Wr¸Ì¤ºùü«ÄÍ”‘9©V«ª.DÂa©"ñ Zw‰ütUVÚ>ÓVÖìßW­Õêûo£xc>èRHUvÖc™» †Õ©ÚµF©Ój—gg¤_'úôñß\OŒþ¦ôn+-¯ôS` „C0£vÌñs¹y~ªThŒãœhz€ìèú1òS¹\82SÕj¥Ê¢TªT¥VK%»±óq z@«R©mÑÑÄFÁ5p®®GèGÿCê+º­J§­ž¿ ˜8ÂNÞÅåý ¸%’{äßOý!,s"Iq¡¿r˜ rëÚÇdnä›¶™L>¢æ9·¿î§] RµjµJ¥T&2&G©ªŠ‹zÓ·ëÖ€ G̱è¸j€ÜY®~ΦÔët*­šQô\ø±›¾bç¾*Ó@?u&•F_Õ0æåçÔµ‡Ñ×+Íu¹y˜q¥U¹Ø8îýË(¥Ê Öªµª…Ê%£Öª*k9qWõ”àèy¿¶š„Ñ»‰2ŽýO.Ënôçmz`w¥M¥Õª*µ©é陙ÙXY>Ÿ:œqIë’µ__©{Ã+°Êˆ(EF^2Í(h¨kj¥‚ùª¨ 꺌´57 y^õW>.mN¿Š·öåfãOOe„CE|Ue(…jß‚ôëøi¥{{°wê‚îUüûîòì}2gü„ïWéÜ·jªªJuúêÕZm¥V›™¾àGÆ ÉÈ"ñ¬›>ñ Oú8ªrÀÚ#¯t™Ú0©ÂÁÛ¹uÍÁ5#Œ© H=°í„c¥²Á¶A„Ѳ€¤…#Ï\ÛîÇ ˆíQ—?.‹ý1pk©×ªg/ÖªÕj¥R¥ÕZT«…±-\«Ñއ׉pôï@/t¹éÆV¾¿ººÜ–¢³iÔZ}¥öUÆÚÈÆï›ù³ÏGùÅÁÏ'%&&]–À…∟ÁqùÔAo‚‰À ±8¼N**¢Ñz®µ­¹!'?oüGuUþi^¬Vg.W«Qfó_¥NUQYΜœ†k¸ð"¸]΃*ëŸ=úyš¥Éj®ÜfÛ¢Ñí¶ÙlÕ•ºÌŒÌŸ ÙD'–/nq\a›ÉÆÇ¾_Ç¿½! ‹|ê8.˜Ù± š!ÒeKR©šZ©sãW!oE V(D¾Å4¾ÆIÛƒÖ«•³SsC­ÔêÕ³µÕªÒ2kùÅ~üüïÈýºOµFŸ’ªNÖ¼–¢ÿLóšþ5eÍVV^©ÏÌÊ6ÓþæŒ(Þv€1ntÐ~í /€·}êèó/ÛG*EÞ<ÜÂsCmÓ¹ÑmJÔ9ÕÎhFž éÈ8»²± ä|Ңظùª´ŒzÌVW«¬eÖ²ò¥»5{†jjjöìQ§j^eÌŒÙm²N[£Ó$/Ujm6æIeŸ²²?p¹<¤ÐÏ2¾1"lí´4¶úòXâSG‡ßdrt¶8|Ê8ŒæÜÚöÐB0̉±3Ì|!‡q鸼zd¦3ÎW#å`þQVV¢ò”²ò²½ådÖŸÕ–’¢Qêõ:N«SW¦×|öšÁRVªª4¤§gü̼ËÏÙٸݜAlôWï}³MˆéBëDè§ž*P@ÊTŽcùÈSBnsnA@§ªÂ‰ uÓYÌŸçSÁ@Ú1/•ÑÙjUŠÖ¦KQÕü9e¡*%E½P«Õéõ•Ÿìé9x»gõËÓÓ ™Ùé©YYééT îÊÈ ïwÝA‚á=¢¥áË|7[E : Ÿút²Ù_8Þ:!&@šÂa ŽÂœ¶±†7šÍ…þÒá˜Ðß 'N˜¯U«±tÓZ&E¯S)uŸ1¢Q¹ïàÁƒ=úÕÈÄÈÎLGº<È1E\ûž°ê2ü±܆ÚF“« Bä€T hrXXhns„œ¢ÎÕù7:tLü/G_ÏÖh5©‹Õj¯x¨5ÈÎX¬Skõz}jêòÌÌìŒììÕP/Í«{+¼ÞSÂ!OŠÑ+õ+ÐO}:ù5P8ÞZ!r@‚8ø°nbÌÏm_bÕì«Û#‘ÎC_7ddÎÏN_ Vk4óÐêÔÅ©úW,˜¿`AzFzFƃáÇûŒá¢…ŠA;Æâ"X& FÐÞpzÙ$¢oý Ç®W•ÑœÛPGŠÇ™[+ôzåEâoÇ(‚>53Ó™™ššªYžjÐÌÏJÍLÍÊHGn©ôôèÕ$w÷ 2CÜÝ {¹Î§¯ç\™<ýþ'îÃúžZ†Ö‰ÙCp`ÉÁv9ÌiãêÇÇ]Håš&Ú7Wå€+iDú‰í æfÖ·ÜOz† Gzî^ÕmÃím=Ãéæ14IQيث€ð3’`uO5}b&ÇF¨¥$G167êÆkhøtÌÍLäèŒÀß&Î''íºžmÎvf¥£|)柬ëÃÜÃ×IÉ[ˆšÒî17¥z(SޤG1öUú÷S¿ «{Êùý/"ÊY¹€äÀA?Ý p¦T8eT»`tÕ<ɱâö†›Œk"3ý NyìQåHŒ±@Ç ôSŸvŽpX©áÙ^Xhæ[ÛR|)xmVHܘßÐÌi ÕÀ×sD"Ä1œýþù8ã<ùqLê³}uäöÞüðu2«üÖ¾~ô ëC2³cŒ6‡¨·*¶þí ¯Áâž“r7ÂA$ÇŽÂÂfŠ:Gµ5µ·ùøÿ(ªÉÜ@…ØØf24´bÉ¡¨:£ w1F$Ä‘ý#þñA6¶)²? &F͗Ĵаî*bØläî óx.“Ä”#¦ZWù÷SÏú4]Šõú Ç_à ’£³°¥D™óŒ…yùù µÍ͵¹ù¦|Ôç6¬fU&4§¼Ž"¦J{nžÑ˜_gîŠÀߌfÃ\…Ÿl+x\븢ï«Z—^!ªI1ÓìÉåßÞ–ötѷƯ~ ½ ¤<3Eåâæ„&Ò¢Ðd2åµSagá’—ärFGkS;Õ¼#27šÕ—ð¾ "-„Ó|ýߨ#,—bÊ3ÐO=zøÝ· £Ž =Šó©:¿ªoS5zS\îîüâmùœÖPT[ÞÄsªØ}o8‹lÜ×Ò±»Ê™žå󬚪‹X+2Þ‹@0þa‚h #6bžhoE߆°mÕpäF~kŸn˜)Áh¦`°®,ê\Á6ü¢‚f6À^›·=R³»=Î Áp}A¬Ž¬lÎêÀÿÞ*½ˆõ$+ƒМ±3¼4v× ½aTqšŽgGŽŸnä5ñÆCS°äªsm|V-É¥2¢ ‹#½ÕÒAÛNqaˆxfj[[qN2«¼q±|0‚TtDwëªû~Û‡°¨˜d|æ6´Úæüm……¦fÖøàbT“™·9 L&Öèö74F¤MÕtºb°¢ú©0Õ8·{Ø9EµæàÂŽV%¨Üf6&^»¯)gÔÂdlÀÊqÎ&1Åüá­èGqE‡{Ã+°¤˜t:yg••cPµ$jÁ…É)*¿ŽSmy¬ñq®yŽÑ”<[TW8±8b­uôS`ØÁõCg,ê\®Éd,­Í\°ƒ2›L9ìMR6HŒÊÌéF±Gl:†ýRÁ.Ãz` p’N¦|TÀW°­ÐhŸËÝFR­¨Ö‚BSÞ¿„Â^ÔLQí¬³j‡4އg¥¨r,Ê@‡_P&–3‘Þ//OZêWíq{¡™jË)ä!‡+Ԩ匪ÉÈé E5˜X3ÅdÌåœU±Q%D¼¢#Ã~!™ó°Æˆ,Ø 1Ño"5 s˜¨ºÐçâ¡Íç(a]63ØD*,*~ÂóF‚¬¢#Z Že°ÒˆÄf‰$üãr#IÌ¥¨v£ïT@¶'Ž&£ÀMe,rHôÈEyëª+ÐO€ÉႨ¯^¬ž£ˆ30p™¸À¦È¥Z¹Dª‚sÞ¬*";œÒ=x¢Žhi]í ˜î‹:ꃴ¦pó1Ž6á„'SÅ÷2ÜF|UÛx“£SÒÇO¼¢#*:{<ôû;݇ÕôÖo\s¼c­Ð+ä⺬Ṟͪj÷‰}çý‹£ª oÀ3có %ë¦âNŠÖaäÐÞ0&ñ‡¹²@tsUürY&S½:õ£VUÍ|ƒU‹f66ÎNí@]­ò…Æí3à(®ŒÎ@Ç¿k‚«°ÞcG›Þ⨇£Dãµò2Y0Ýå£Gþª‚6 5W7 tsL‚¹µmÕž³½Ø3#䵨œÑá¸O„œxË %¢óŠ`º±t”|gñö‚¦ap|[.%œ-n2 ŠŠ3åP>LGÝŒÿ~êÐÞ06 Ž·„€· ˆ¶½.)˜l$„±ß9‹‹ÌyÈgeäš6åù=ŒEžt4G¢¯uÕJè§‹tøÇšßáˆÑÄÊ`²![^?#Ïâ"päÕ™¼-FÕpδmt<´7ŒEê}„ã­M8$@ÔðSb0ÙHü)Ü÷`V´³³¸h;Òca^>®7n/*žyªF>}­«üÚÆC{ØŽ·6ƒrQÂÈŠ`²!C)ûL£³³¸xÇû;vì(.îìêš¹Ä+:¦)ÐáßO}%,ûØ ÃO8 µ ˆ–Kã`9¸²$èI1¢©uÕ5ho“x†ý…ä@40¼4˜lÄCÞÍDÏú•ãJR›  Ÿz¬rÂ_8Ö@ 90í\ÓepQkNŽ«ÐÞ0Vù=Àä€90Í„ÈÁ…ºâÈáèt$A?õ˜¥7@9NÀA¦×Ê æÆJ'žÈðHŽû~Ÿþ¾—¢Â@q%hnôMÑ0£ú©Ç4Çý…c8«€i"xc*ùX–åòt·®zà÷ÙWà;‰-6˜pŠÓ³™ÅmLÓH#èP\›ªÏ‡~ê1Îïëü„c#`x47áI0ï–NçŒÿö†—á ‰5úÖø%VÁ!¦rp§œ Ž)QŽ ÐO=æé‹˜f‚çà&‚ó{ÒF.KœŠ@Ç2hoûá¦ÏÊà© ñÝdêµxEǸ}³çä4|±xÞnz &:ÓÆÕà© ¹r è˜ü `pH€ß ÄLÛÞ"ŽÎ4{“ÞºÊçK—À›üêŽ_áhSHˆá°áèLÓ3£ã<ôS—}¬·jS `êx©¦ii]5"øÐDˆcÅ.tÇñu›Žw@ñ0…‹nePscx/¦Œ 3:&·¢ãŸÑ%‡@as?øpXhL5• tLnëª+ìEƒò­—Ãa÷€á;ňVtLrë*Ïå¤Ä¤$è @ø[U¨á° SÍÃÄéªèàÃa0vZð$MSE@X@cª(Tqסæw84L?!†ÃB t¹&Žaä~Ð!S_dz]4С€–çL/Á‡Ã&Â¥ít¬¢Ž 0)‘h¸VÂÙ™B4¦‚©ÓO°aäy&°ª<šv¹F#C#ý·»owwߺE~¡™t†ÃFÿW 2¸¼ÈíÛýÝ=Ý—.öôôt3ÜFt㛿" £sÖãä½uðá°ñàF‚ #¿2±E;ÁÌc„7+Xzº™¸tñâEôfÏžo¾ÙóÍž='¿·êÖÏÝ÷éþùçîîÞîû·nu÷þ Ç,øíe¹l,(^{¬<ÄpXŠG-3:B-#Ť/# V…ãW"H4t<ð&åvßÿùÖOH9º¹ßýÓýîŸoÁ1‹z½,;/ír1ÄpXÈÁjüË‚Z'S°Œ€X¥Ÿ¨Ö†™ÿ~}ðàáÇ÷oõöv_al+W°µÑýS÷-ô„#êÙ'q»Æð+ƒZ4+`Òt”ãèæV<6ˈYîÞ½{ï›oê¿ûî»o¿Ý¿×®W^ÙÕÒÒò©Lö©½Å~êð‰O˜›ô¯üDze¯,;yòd/²è¦cŽl¼Ìí3¾ ÃacšaÐ@|ée´Òsg€WÃÞÒ²S&ÛÙÒØòùß_|ñ…?>·“Ž—Ð]--g¿ÚyXØMbçáÃ'á˜E3ô˲  x9œÏ ‘ƒ >1±JVŒV8¡e${±Ô$*Ñh·#áøÓ |æF1ì/ɧp>|Xxå±sç‘#pÌ¢˜î¹²‰1·{”Oð„ ÄWãCMJ™ð2šŽ ©{ªZìöoNœ8Š„ã#V8ì-ö³uÇÏ2¶H##Â5ôéÎ#àÁŒbŽ…²&æ¤-A¤-šêœ?ú#‚7¦‚á°1„çZRbRÒeÏ8—QÚ’‰-# æ…#g C޽¥‘XÈUuü½-[Ñ]-Í;N¬†%‡w‘“QKpÿBÚNÆ€d¾Pû©KæŽËÏð0SÍìed'ˈùÓÞvd¼Ëˆu:Þ{ Çñ{#¶8ž}&îë¿¡{¶œhi¡«j‘P8>?²J‚£:-ˆ%°äˆW2ÈIoÇÒHbQðùÃ'‚åà^…/@¸Â^FìbÇ2bç['ìv;ŽYé[É]Ç™í ‡ŸÅqø½b'¬ˆ(ä7Ñk?yÚ‘–ì\$–V;7X$ W¾–„¤—‘,M\5øe$Ë2bœXÙØòÞQ$ ;°÷ü­ÖÎ\“¶#á`¯'ä2…b ó»yË{°MD¿Š¥OÎù´±eN-ƒ9¿ŠÈe+qzÅ—‘óP‰Xì2‚ùL¡k;'ŒqÁ¬ {›ýèß¶"{cëßNÚ1ßfL ?Þežð¾_ôŸðñ‡í-aÐ("ANùeâ© WÊ—h¾ZǰŒ x©¶nÍÁÁ\:4æ£ß™É=èÎ#>šEä~Ù‡N8„Ñä`<áã´„Kc §aލ›!†Ãάe$—Í:ì5+F3^ÓÂ\F€¼T9È E™·-9ï!1·ñ1Ôöþ&ºýõ{ÄLUô@:¦_jöÃÉRsžÞD‘ÆT`nHx)^²e‰,#X’ôR}Ðtº…ÇÑ¿‘ øßŽòùvö–öÃ"p¶ ø«¢†‹aÑ(±öéðQ§…㪂\)`1,jkËhVÉy©¶|àø…l0öF3¾cÛ{'™ß(æ÷ ÉIûç¼\xVCî®÷Éë?„ˉ¨À?ñ^ñ©ÏÙÞÙ8ŒDü 0v-#ù§ö–± GËN9ÔsH”·x-†»§Ïä°ñŽ·4¶76~ÛÒø¿¿müîìÿþöäþ#ûO~¾Ÿùsÿþ]ÌÌÍýCŒMËzº¶v‚tL?þ…¾ñ§ZÆý”¿ŸáX€·Ê“Ãaa…Ž–Àe‡V üÈZ ïu¡ßîžf8ö76¸qö´Ÿ,B?jXÄû«º@:¦™n瀽e\ØýC£91÷a8¬D¡{–Ñ8×QÀ2ꆣ+/Õ‡n´áÓ÷NŸ>û7VI¾>}:,áè!oÕUþª¨À/¢™6Îó½ÅÞj÷‹•Ì ü0®¡: ‡•~Û}Z8‘1¿bî—Ñ—[üÀ7Ø;¾;ó±wåŸ=-"%%%Âñ¿8¿Å[Á_5ýøy¦—´LûÜQýÓž•K–^/,£ð—¬–˜¦ë=62ÑÅßu™»ë›ÓbœÅÂ᫟ìspëÀùUÓNDzpÏxÿSFòÀ2Š€rÀ2’„—Š×Çû\ƒ‘³§Å…CÌUURü¾ƒ³1~dóz?€zÀ¨pT¥µLðŒo›É”3Ú×»´Ä>Ñu4œU¡sK€uÀ¥Wm1Ÿ™KD„ãCPx>d{"þþªéàXDuƒ19}[Âø•Á.ßË…– ã·Œ ³*Fál‹­^ÇW¸õ$o`ü÷·¾ÊQ"frœG/â…ÂɾõöN8ÌSÎ@œOƒûÄÏø–S>g|Ü#8ÈÒg$âËÈ#pcg[x3 œ¼¹QÀ¹©NýýO/ü¹&”pàßJ>Ù¿U(>ÃBSŽOHSq*ºÑbÿê·`Mœ°Œ¤bn6öÎ÷ÜTŸÿãÅ?½ð »F·8öÄÄ;‹ Ô1üæsrn‰ ¾ahS'yú}¾ð#ZF‹`Å6x©œlÙgtãOŒr,ô‰“ï޽4'E^¡`¥cëp¼§ëJqI„Nx?ÿ4\+Â2ß2z–Q só½c€ïŒ»¥X ÿ@#Ç_øã³)>…bÊ1ÂgânýMÑr¹;߇®¹S̈p‡ŸÓ8Ž“[¼ÕWàžž±Ë(Þ)áðsVÁ2Š)~ ¶E‘#ÀqÅh‰çž@"þN„ãÏ Uå¢Âq¿–Ž­ïñ† ÍZïC”|ŠØÔÃÐ6±S~$VÍØeDwѬ¹6XF± ‰ó$šæ‚â[‘–ü·Ðà@ÂÁèÆ³³ÎЏª„RÂ6q×û]¼ÁZE S¬HtãˆðŒŸzæ,£E-“µŒæÀŽ-‹ƒÙÙ½^*®Á›NûW">úû‹/þ )Ç3ϼæÓ¬ªÄ?­êã’ý¼õòÁVbcМ.qVÇpè'úȧˆ6ëáPKz á…ã™g޾”CxíÀªÑ{?pv Íæú¾ß‡2ñcš§Âv@·9%W—І–aM$³[Ë(&éâƒâÞ„7꣼GÜYp”É!”Žb’Üû!߇ä‡í “ÎIa3 Èœçmâîé“p°a…“Žz¬$,£Äë¥úPP¤Ç›Ÿ$°8Âq6 2.Éá}w¶Ã:ß®ÝýŠulÝeSãbøª%Òă“–,£ï±Ú²E¤-ˆK B‹c—p ‡híx ÁËI7ÐóÃÖ­ “‰àœTØ#~Æ¿$Ì퇃=#–‘¼qbö…Âþ5qp°c ¾t£¨ËÇÏ8xÚÇâøgqìä49J>Þ'ò!\BUWˆ´äC8ø“EÇdåâ²]ražÂÌ[Fa®Žsã Ã2ŠM/ÕV¿ò ú®·'•×âðŽ3¢YU—ˆ[ƱåCgu€Å1iìš„.UÁŠ¡x –,£F'ÜxÈ[8þ‰×âxö™¸W¼…¼‘áct”ô‹–§‹ë%€“Í"ARUã$œñ/Á<§™€0ÄÑ6ÉË‚1è¥*vÒ!„ã#Öâø#¶8– Ù·¤¤ävðü'ãnÝþ!tÊT¾é98±Ï…Sö AXF¨‡e;ð1q±$j¾ãÈçŸ áàbÏ,=º=nIÉþ6«­À|ŽÉã·ÉhhêæP@SlXF_FrXF1*[·u‰?ø­¸pÄ% „#Ü´*œn‡ÑN“ËEE oZ&û”?a…·¹»¥åð’¹ YüÜOEÛŽ@ש.#YD—‘ýÈÎEñ¾ –‘ü"ðXŽ ñ†o…Ù¸'­ªp$z+Ê˽Áñ¦#âxˆÙÑæ_sä² Fü²óÃ"Gl—H½ïÎx^†âùâá%03ZúDx±kæÓÀ»`ÅšpxÇÅpÆ×â`»ã>×d ‡÷×èíü0ì:އ‰ !H|xÈ„ÃwZ&~ÆËe‹vžj<µSám—xÆðHá2²¬ûØ…C>g‰ïƒ°Œb’PÃ\ioœÏªzYŠo®*ñ„ÜÛa|¶ÇÓ^§Ã+r‡È¯3A6L|„c +§œ«Û.k* ­J¢L|ù”9e÷Í‘iUàj˜2ÇÅ…ˆÒ9íW8骄ͪ‚GO¯ìQmހ܆¹"Ù0‰qx;asˆ°p.4¨“&sEU…·Œ‚לú¼¦Íg«!ÊãÂ[ÿ·×78>ËW8Ä´c¿'r‹÷<|§£rÍÿ Å Ìg<×§Ä£…Îøs²9;íö y÷ò€¸Lá‘,ñ"uã]F~b#¾ŒÀÕ0µ\‰´p”sÂAb³ÏøYÚ±/‚‘ßè¨$…8ã—ŒíŒg½síA¼Ô ‘uÐfHúÂ!²Œì¡–ÙôçŒ*žjàj˜^›#2»öwBáðºªââ’Î6†œäT´éÇ$9ýšÀxiLÂ/K;Õx*-Xg¢xïýs‚žñ€TP;ƒï2²‡·Œü„#ø2WÃTs9"‹æÛ3b鸌r$5ŽÖ×$Ç„€ëŽ0®üšLT8^²Ûím¸Gaá`7ˆ%2Ù)»h7Ó#bm†`‹•&‚¯øSá2j´B.#FX–ˆ  .2¸bŒ2ÇŸ8+ÇÙ¸ÿqÆo Ç8ÓªÀ☼o\&z©(óßDÎx;¹œ”Ë–‡ý”L¶ÈÂ1#…clˈíäoWœƒpÄÎõç¸8sÚÏâxïUuÆÛW©ÄÿíuQqÂñ¿"·x¯Á:öàø¸ÏøE|úT`o¢Sñ²øFѹŸ°Å‚pˆ.£EË(láWCLZC§ë8þˆë8^ó DZºñ0šŽŒˆ‘#ª¥opü%;—>¥ð37ZçxËÿ Æ1Óc£,#>™â‰qÇ8ÀâˆEá ûOû¹ª^ä‚ã+Î4%HŒ£<‚™ý•7 ŠÀ<º8ï£iã:ã…£Ñ#âé0Ð×TúÂ1Æt\>(&?«*\ Qæ¸÷±MEÅŸÿLšãÆ8ã×W]LCÿí¬g¿ýöì·_ïÚuÒûH`_õ’ÈGÇ¡ó@Ä{¡zEÈÙ»ìK¸3>-ˆsÚ¾H&ûJ.·‡,ù¤A˜ 䜡:ê2ò{½½5Ô2WÃt;.ÆY¸{Údgœñ¿so°´ª8ë¦A“!…=øÏ•e s"d: ÒAÇEÃì6‰"ìÆ2j,#’'-«*ä2WÃô:.&êª A¹¸½Ñ´*`ì*a[Óý°ÓX¡Ø)«ãðö46ÇÉû"mM¡W•4 5Š-#ûèu¡»ã>WÔ9.¹'C8ÎŽÏEc%M«ÆŽpBˆ ÿü¬˜Å!š Â1‡EÏ Õ71ýÔîŽÆ—æÈP[Ó–Q„CF â¥JŸp±Æ¦\|É}—‘,£¶–ÐÂa‡e$5„鸟xKÇž %^§U ÁiExB¶Ÿ¯Ñæ?lÇÞ &dãΈeÆÒ°õXF’ã®O ·çȳ¯|ŽÅññÂé$MÄ;IvBRÕL[FGì“»Œ`ޤ$\U·©SŸúª?ûÌ,A!ÇY1c#Òýq±# k.‰üo_AMXF–‘ÔÖqìe,ŽxgÇ>£ñÍ?VX…ÓŠ0¬ùÞ®€ æL g’—‘–‘„]UŸ|ò‘Ð∛wF8;¶D¼Í!Çô't2DüŒ¦ßÃü? óÄ”-#qHÃUu÷´Ï@a_õ$Á@Ž`]¡cšz§ÓÆø Ÿ‡t1@ˆcÆ,#û$:Ç߉ÅA\U³Îú÷™ì‰ÀØ©ž–;#êšn&²z¸T”ô2æÏí´O–Á!«‡C- ¾ÇGœ«Š³8B6«bU 9¦YúgMÖµ¢Ï•â8ÒÒfŠ–\~H¯8œù„›9Î Gü·£—Ž—@!Çt#,Þâg‰Gºj ʶfÖ2je„äŒ@8>"®*.«JqöŒ¿Å!â«êkˆéeDèdPœš”ä{™<’3iÉ&iÉaI×i‹ã£‘«ŠÔÿÅ)Fµ8J`”S Ãã=>ᇰ´PdøË( ãaIARÕY¾pœ³8¾ Ù·*£ƒ~Ÿ!_‘juø’Á­°aM|AGuÉpOЊǽ1ޏ ÂQ2¦ÒqúÁe˜Ù1…׊ñãñ2ÔÛ}Bšp¥Ë(‘qXFÒá;_‹ã#o:n\œL(%ÁšŽ”sÏÃko<ÿô“O>†ã<©øº§çD">î3yG7–Ñ„—‘âd©ðí™`®*áø¸d,€ÎûçW=÷ä“O>õóÇy8Γ‹OFŒlI¤sïaØøÌàØ$/#H©’ÞÄ©3Ü@68þLœLt ǨÂáüéÖ #O=Â1ÉЋ"ìŸöõLÏ…#<3˜;ŽeÔËhF 'gΜýXЪ꟬ª3ì@ÑÖê.n÷r»_¹°êidds"wÊ*,#\¯Ée…Â’Aü›­ÿãšãÆ)v „£äã`µã¯<ùä°•ñäSOûkÆ Â1ùôúž¦Š# hÊd½ppgì2÷ˆÝºáàJÉ8=í+ÂɱŠgŽ  ÚWýãÎÕ °8¦Œc~'ê§¾'rØ•¾~¦ dTÁ2 /î»Þ`ÍáðkŽ·ËëÈ:]¤«úÇÛžzúùåA4ƒq@àà矖¥'ÿ”ÿ žiXFm4ËHr< ÇÇ|C"2«Šï«^â7:öã÷¿> `qL þÎ9§BbW/ÉýÞcVXFÌæ†ñMÒc$„Å¡Ž@FˆqL‘ù8×ÿ|c“õs"š°ŒÐ2jË*:ËHú ùÆ8|úZŸ Žï{,Žèà·€36~ ÁÍ&ÿKMæbšÒÁ2b˜u$ü(Ù)XF3€~¡Mñ‰oC_‹ãó ÑñòÕ£™Wá@O ½³OÚSañ§> Ø-˜×BB•”ñ\NJLLiëÐ;gœËÈÎ,#,£À]_WÕß_X Åÿqzô€Œ rm4á‹cú.e²¹;Çs•ˆNø_á€J˜Ëx‹—Ë®„£XF ’ÃuW<—¸ª„Âq&¸p|| ,ŽèQ޹"§nüK!®í;çˆï²¹à`²¹±Â[æ÷0бHl}2L¾s.,£™mqp=e¯„eq||,Žè½ê“ÍYrDT;ì‡Ó¢/¥A@S²1œ$øªGưŒZƸŒ\p´%.埰Íqùt\á( äØÂM¼, ¢%;Ÿ:Åy£O9üiÚœ`Ï ’x>))1é2ãXçJ‚Ï—}aLË(Ío½„–‘b,Ë”p|ä9þ ²8Ëž-ñŸÈQn>.dUM-Çda"þбPÛüä˜æ‚ß·½lBËH6ÆeÄ<®@WgqÄ!‹ãŒP8‚ŒŽe„#;”l¼ñº*ǧ–Þ¹<ßçŠ6zèšeàÇŠ]F–ú߉bO£'¼Œ ,>S,~ŽŽË–žÖWÝG;öšC ǂ矆ANS ÜÏâþ…G‰[%ñæåabÀžä©\Fpu!M<Üõv£j,ùD818dÿqZ¼¯ºŸÉQb6Ih@Çj8ÖSMÇø¯çv„ãß_ƒƒ“\qP. öäžñ/£90·IŠšáyh^õô“«¾ bqàq2Íi±ö¸AróJ±Ècj|êŽcqã:ßÇÜA.—ùHÀ]‹'þ ±/=¨KÀãÞ?ÞeHlé0–ÆyF4þÀìè‰'ý„ƒq@Y’p”„—»’ ¢Ï­¼ê„£> ü>GÃË#Aß/ÀÃÃ' ×ë© ñ’‘q-£Gp¬% –§ž;(ÞŽ#²¤3‡K‚6Vßíëžzúin‚ìÓ«® C`|Ú®~{Y1¶ËÄ—Ck%feAð*¶¸’ š\÷0ô«ÆºŒä/÷á–’¡áy §Ÿ[ÀìòBá(ûèï½èãªJÌãð¶Ç- ˆvØÞðÂÑ @lj<½êÂC8ìÓÌÈ®Yá;¥w ‡|¯e"¯wU,qAü‹¿Æ2š3†e4GZB ÑxŠ·4¬zÝŒf/}sÖ« ŸøÍq’1Â!´8DûT‘€«Ì¯¯Zð%>mW¸y“į9WA7¢ಸÝy!RËhÑ.H¿•¦pøZAX¦Ñ)_øã³³ˆÅw¶ñŒŸp”p.*F3jV,` §PHÃü\mD5¿uìzyÑÜ9蛕)âã禽¼«£,ßÙñ+MpWE¿Ñ¹B|»¿2Ž÷ê¯?örÚÜøx¼Œs˜et¬:àJ“çà 2æ•yâªUÿC3m/ò³þÑ’}¶=«žú 6 þÔÓ«Î?#5&6‰½ü¸»ÜUQŽx®,i Š×ÃF 0«žîÛ³~#KJJÊw¯äâàÈÒxnÅeiÌáY&®]͈gáÊVÀ—DJ8²ýddÕÊc'¿>sú #Ö÷Úv¯Xð?ž~’ j<¿ò*\³Ì0‚øÊÁ]½\3˜láìmKèÇNîY± •ö±¦ÆSËÌW  |&òÜU1ÅÈ„²pŽ ±ìùDlh áxz™ùMƒ;C ⮂bÀXÒù¥P¥Lžp˜‘ï 7ž"§p6ˆ¸«b†kÉÂ@8ÆÄ*"\HcÅe´5@¡ÆŒ²«bñ^¸²„+ph€IŽLÔâvÕªù\Hã©§žz~õUhWpЫdP õLC.}y©"iéJØ*fžp¬2¿n^µ€”öaSãé~Â]’a5£ù@À]=\‰’…;y'2×\ ñ>þ' žç[è>‰B €Øîªè&HîåIüHÁTzPŽ™$ËŸŽo<…&0AiÜ+Å€QŒk²pé'«@ÂNË‘U|AøS ÌÝnhŒ„²«¢– Y¸I“:™ï2$nIŒ7̯¿n~#$æOán…îƒbáîªØ’ôIÞÌ}LÐØD¤|“@¤Ë »* ¿•iÊÂã`AàÚÜUÑÁ´õÂõµ?—Á7@$€»*ªøÿÛ{· ¶Î4ß{­¥Ã´DG bÀP{ÇP55Õ„0j$ Þs‰§{dÉXzR¹ìËÜNõítÇÙ5;éæ°÷ƇݻjºÇPõÕáÐm‘Vn¾IËӟ쎨iÁ÷>kI 08‰ccIþÿl@'NKB?=ïsxªÂ}ñ ûÃOéqÀQ º*£x‰³p[÷ÏyÜ€#ÁrUÆðRgáF RÄÝxÊ3ÆÕUxÍy̼äY¸’+جðލãÁrUF„~ÇÖR±€¼€oÁ<–«2ÔÞÊã«‹ípŸž –«^2/­ 7ì2øš¼G4¶ ð8xyU¸iÜ;ðÍq·¾îiËU/—0 ÷D‡±#€o–«^/µ 7|-á®|Gäg¹TW½HŽªÂ=þE‡±#€o v<~^vn:ûÆoãÞ<ó«_Ì®zQˆ/½ 7Œ<˜]u¬dBnÆŽâ|#P]u|dDn:<†ûcÔëõ A¡à$qŒÞÒæFÜ!1 7óZìüØA6{EQÈŽZ®úfÍ€o½U\œŸo0p¼ÁP]]}¦¹Ùn³õõ¿70p±¯íªÝ>040PuFÃÐë)»QTi2UUUU ‚öDEÏçDØ–áU¸éìïOTcÊL½æå E ^¯ÿ¡Ýnon¦…«¡¡‹6Æ`¸{×bÉþƒ·tèZŸ¡Ýu;’µìoÿsà€€—γ-WiIJ,Žê‹Õïõ7W77Ûí?$q\$q°çTÒÇsz½%%Ž¡_]Ô3qܹÓÑýñŸùU¸ébìH–²¿ý³qAð­gW8¡Ö2³ð&‚Ågª›mU&†Šm N¯×4:½î4“ÊÀPÿ€­¿¯Ïfø'¦˜SE<¯Q.ŽMM _îèÈî·}¨r w2öÆØ‘ÜR #ø–ÕU'NH|£©ÈTUÕÜÜ|¦ººš‰ƒâ&Žïëÿ‹^ÿš^§;eìëx¯¿¿ºÏÞ?0`»h«6ž&¹ŒOM9™íâ˜ÏØY¸Gò9!Žl§2‚#–«Ž¨õתyŽÚúL&^Ãþ¿nÒO½a2é‹M§OÛmv;UTõKH­,â(2UV*|mþ݉šŠó7³û¨RXÀgvÆcG²”}9¼2„#ª«„CŸZ´ZŽ7éO™ $&SúS“Éh4±w¶Jâ aôQNC‡))޼!h•wïVTt·f÷ì*UvTá¦q;Èf'›N¬TÌ|N9<Ñ{X]iq±é ‰¡­ŸÅ½ñjß{ýƒm¶>»ýêU»mh`h ßÆ®¿ÚÆ.9SmÈÓäåå©“Îà¥R+ÎãḒ’Œ’òòò’²œGc¦·FˆÆŽà¥k– ¦uŽ;q¯ âðåªùCÅq¦š‰¡¯O‡¾ÿ*uoôIâ°Ù†˜9¤ˆ£¯ßn·5›ôz2Gj‘J¶‡^Gy9‰£¼‰y#+Åq0J[Èü¿èÓ—ð°Ïsì¶Ž;ÐÄ2ŠC««¬‡ÞTàúªív«•Šm9®èt3ÇûÌmý6ÛUvô³wúS©œGºàöÔ¡Pt‹co¬ì«¶VÑ+ºD«}œel!KªpÓcìH¶’Ls¸7p(@†qHuÕáCT9Žz6ª¬‚Àqg4^¤4x‹Aª™A®mý¤~O‚¤éM¡Ð©ÆHEö‹Í¦ª34üP—¯Íº¡‡ûÊq ³¢+B<VbÊjö°    d O.W^²©V_Qe5U ¼g² F£1Y)þ(2j4’& I ·r¤ªAî÷¨>óÞ{¶êÁA[_s³Á@£3†¿c|ý­Ò[²äºõëW"à[ñÄr•õq(¦êê*žÿøNM±FcµRüa4’(8AR‡5r»Êàä+é&Žê÷¯UÿÓÀÐà@_uvŠ#áWqÒb2kæ>E°ƒ,à¹s`vÕáÝÅœ4ˆJ©¼sÇ盜L.AÑnwAŠNHϪ»iŽdÜÁ JeeåÐÐ@›­²’ç 2g¡êïvùѾþp …ŸeÑúÁ±#<âÏá5iúr•áðEpöìÏ®Ô*=$ŽYÉ Â^>ƒãù}Éð=¤—çL8µ•Ik ýꢭ¹¶’ç´Y*Žì{a€dÏŸ¸uwéž?¢¡M©%Q×ø|^ÆÙ³»+Pܾ*ŽÛ'úPYk2UW÷W_¨¶µ±hãõ"µ:ã©~´KîÝ»[ÆŽ^ ü×T˜jU”Ũ©ñ2qLyÏ6쯸åRKU<°—«4‘8l4¹Š½]í7š Žãå@÷IwÀsA¼Wh(,<ºä¦ÜÊó”ᘚòz§½““çÞLEÜØc×\‘±Ùj:sq m€v´Û«Ly­2ú7þîÄ~qüä'9wß.eÏPF@N½jmdâPÉâðzg™8¨—çÓ–¬öN'/9UdµVU¥Râv»©Y“§TfX÷F*·‘Ãâ÷OJ À G«Õé ŽÿxÂç›™™žžöÎÌ8([ž¶2%¤Öª ½ÞVM»^½JûsØì&Ókz¥ŠãµÚŒ{ÒÚ¯œÇÁ±#ØAðâÅñ=A¡à¹É;²8|Þ¹¹Ë¿¤öd !ŠT>\‡±¹™ÌA åm¶Ó&Ík •À©!Ž—AcGÇ­(+“Æê§››M&Š)*jnÿéöøíãã7oªÕB*+Îét4pý´41×Þ×Ö?ØßßÌ(4òjêÚ((ØÿõßnÒ¾üùAmä¦8ì 8ı±’Ç)½^ÏÄQEEqÍ83ÇÈíûýÍ[…EIqpÔ6®—ÅñÚë&&Ž>»½¿ཾ«,îhÎË#qŠ Š÷ƒ¦¦ Èv|#qijþÎIJ€c ®\¡0êOŸ²Ùi§¡û…ÊJ“I¯/Ò…%ùù&ÚÐÈ¢ÆéÓ&S_e4X¤!å5L¦Ó´Ã“Q8ö¯ÀÐÑ19[[°¿WœÆ[e€8~ô£§‹cX"ËïÍÆŽ^š™öNLÔÖj¾'ý·~Ìó7GÎ6œ?ÿ’ŸŸ.Žá4²û½‡±#€/•ÉÔO™ MØ÷>½ë»ÚÇb_Ëâdâ`g~ecâ o™8(#rÚHÓr Kþ…wæç›¬##33RÓ`í÷4Iq¼Å 7GšÎgpÄÏ!qÜAqÀó‡*ªTJNP¨ÁzaàêÐÐЀÝn³ ]dÿHUÍCÌ%mmmF¯P¼&¥Ç©ƒãÃá@`+‹b<>?ÿî»33^¯×71QSc,¢¯.¨oLz}ÃÃçßÌ0qüä'{â ]xî2&_MMž$µúî Ÿï£á¦¦ ÇÝ»²9²^~´rŽí öO[XHÝšä~âš¿ÚÝV`º( Ë “É`P(&|>oAAlg9ÃbbaA¡ò\§Q%SSt'hµ#;7<üî»ô]Nœxi¿àÑ Uwíwåþ±#|nÀ‹¼¡NŠã¯Èr›¸"9ŒŠEBRE’8XXá+.ˆì„ÂÑè“âPª<Š8HLœ¶Àã™fçœÃ­Ý웈™)jÈo9 ì 8.Ø3¢©œç,555g- g‹‹-–Š ÚØ©¢¢Á97^PPR'ÄGÛ 1OSZìóYÎJ›Åò¡˜˜Ÿç¹ÉYŸoš155;[s–iÃKç>®}3‘àù—ØA~ôBÕþxcj*ÛïÈ;ÈÖâ¡ x‘””«ÕçŠk,5ËÙ³¤&pTÔ44ÌÍ %L‰Øv"¡Q—ú|5gäÉUP‚ÄÁONî‰ÃRã¹>-1<\_+‰ãåu¿:â8ØÊ±#€‰ZÝÐ@µ>Ÿ¼ÿŸ×{ÝÃs¼zrrf¦aXÍ…ÃR«œ¸±ÑÝ­Ê¿{×s]©”çZ›âñù!N¹=7ãõÎÌ0Uœkim8wƒ}æÔ”gØ`P«9^óZIYê{½°¾ŽH|8Òµ‘Z§š"f²þ^<8vÃÕ/Vçff¼ÓLIqx<Ì ê öô?üKž‰C”žñ76šºóóïz<&‰&k<þÇy»}{fNÇõsõ­çÎݹAâöHâàtвú]qldŒ8ö¢ "ëïÅd31ZY‡ÎAΟ?Áb zöôzi¹Éëö( 禽3ÓÃ#m­Txێʼn¸øÎµ†Ë^¯R©j°>¶£yçòó‹-†âb•jñ3ÿRk«V99ëú廂 V7ð|·KþÓy먊ë¹þð¿Jã0y.Oš2r@ûCŽ(ØÅ渳1¶‰CrFwÒÅáóy>R¨Îý‚μÛÚیǷ·(Çß¼ÕÑÀÄ¡"qÐàÜXLó:Ççç×Ôh‹‹‹•ÚÅÅ%k«R799Å>Wà˜8†yþ|·,ŽŸ¾Å¼‘ ⸛ƒâ80v¤ìLb£«]ÆÂÁ9@yI~¾wÊëóN{“ò &'gf§¦½Þ‘‘ùwâñ ê§÷óó#ïúص{[R®ƒ¤xnðØŽÇ#ÑóMÀÔ”‡Å-7îLÞÑÆÆâ ­ÄsÎrüú׿:ÀAu>¥*·”!ß¿V¥ÂªH&Å®öÄ ÄQ^@âÄ1Mg&ÿƈLyÏ ÏÏÇ%e$’â87B{’Ëût¤ì¡VSÈààNŒy#œ3ðú<ÿ¢PL~<9©Ó% ŽÎd—ðØÎÜí{Œâp€ì篾—ŸOÕTÓ^y©ŠÒÚrä1553ûn÷ââãÅÅÂÿRh9!úý–."¤YR¯y÷ !ÙÜHˆ¬ }M)_ry˜ÝH­=QV–H<~Þ?ü¯ýuêøÉOž,ÅÍQÂ;’©ÚÓYÇYOž†’ãr¼‘.ŽiI7ßýLG¥ß¿J$üþ††)ŸoBH‹8øä>ƒÝr\’HX­|RÓÓ—‡ÙUj­¶®ìüðG:ûwå·ñàÎÖ÷y£Yí¼ý¶F3<,ÇôFÉqJËiò™¹wnýaüòðªû~1±´TîöînNÞŽ<mC8aaaK‘´±amRðLA³ÏÈG<Ÿ—÷÷ÿ‚~üÃÄAêH©ãUÐÆŽd*›]ûÅÀ!YÎßÒä _—2iâøÔ7%‰cnæïÞº½¸(!‚‰ãܹ‘›ç]BršUj "Ç ÜÂ-U‰Ôïq^੯ƒ‰câ86"ØA6#k‡8@nQVÆñ´8åóÊÚ˜™™ž–£©© ßÌÌÈÈïÿº^^Ž**ŒÇ—î©ÕùZ¹s|w©*9±µEŽ7âñÀ!y³©XÛàlh(.~a?þáâxR¯†8°ƒlF8à ä8@¶#6Õq‰CNŽO{÷ÄAHffnŽüþ^žQvCaaü1‰C«RªÒW©ä CZZY¸!Õ^ñÕ´óÃî–{A-Uµ´j•ÃÂB"aÐiU55wîøî””——‹/ª§à×I“Ç~q¼÷æö±#håxùLp`¥ äÚJ%‰CNŽË³ª¦’±‰ãœK“·çˆHÔïW)8a·7UP•LŽ·¶*U$1¡cœeâ˜ð•——”' ŽãáÀ²a<À_:£½Ñƒc²_ÚÉ“£‹©)ßÄÔÔ§“Ag4’&&'' ‹UŠTÄá÷ó{ÎHµ' rãñ•Xt‡çÔÔ¸¡öNMOû˜Œ´/ôøõ¯–Çž:R§þñÿûäÒݹ„±#Æ Ç È~qLN½MÎÖTP´ÀÄ!pÜ$ Š‹Uê”,üþ½†?.­—>–ÔÆã;±hX¾@­¦T;Å//x7Žo!öü<ÇÅWµª—ÌÊAo ‰d?o¿Íó>ª¥"y>†/Óà„‹-tŸ?o½PTHs¨„4Q¤—R¥,BËU­­‹‹K÷èü½%ëŽg_kxäÆõaA ¢ ´Å…ÅÅ%%%%åeìí¹f=~½'Å‘Š6~þ?ÿù?‚¹G1v$³è9è1ýâhb☘žžÝǰ,ŽÍí…ÁóMVk%‰Cà„d`¡HÎI 6R>ik]üìž,Ž{V+Ïû¦Ãžöõ„òµZ«-((`â`Î(/)+/AQÇÓÄñsÉ9.Žè~q4â1žYGb@툒8îÞõzÿŸi¹õoÖgi8>¡y¸ÒÊG<¼Ö­ÕR6\à¬Þ™¯¯MÏnì! ƒ‘HQ¡Àn˜§ii18¾¸FÁ8¦¶¾»;õ_à€OÊc¼q´4rIì ›Alýs …d·4˜ÄÇMo3qLÈ}ÓÓ^ßì¬Ùl=¿³#‰C™8üþîwU*AêÓ°Zækk.XÉòXXˆ=*,g‘‰``âÐx¾´F¡Q(8N“W_ûVSÚw~ÑV‡‹ãiËT¹$Žƒ;È⡞Iâ@%.È^ÔZm™ö„öDySw7ÏOÜš’ÎÌ\÷ä‡Ã4ƒ–Ú…#†ýþ–:cÐ2·lHÏS«™  ‚”=g‘çù—ÿZQÁñ/õWýoôÃýüçL,ê8,=žKêx„±#™+$8@£%k°MeMLwÿ}OO!&YPdÐÚBãñÿÜY’űÛÚ‰Y/ìöpì&8äa#¡0‰CH&A<ž7*jx.Ä,-UQŽã© V9qï–cÙÌa 3V6¾©Õ*UjA¥U*i©jfÆ7ñ©oÒ£3Ps_¶°Ð``W) †ƒV›W¤ÉS«Û#ÒH\l´ r q v‹›±Èyëü¼‚ÏË‹Db;µõ€Ï÷©¯ÁÌWÄñ?%žÔÆßþ-{÷zò‘"£ÕñÁ9pï~†±#™Ãfº7œHp€l¦²òÍÚ|ížÏô1qWîãðù<“:¥· Å$v–é£X©5j4jõà;‘h‚v†Ý²6 ©¦q¦Ewk\ŒDšÎÏÏó¼FÆbµµ$މ ‹%3Äñßö.£ #§Å±Å£®*sCÈx®Æò©ovvfŽJp噸 ÔÔäç á FOˆb\.‚ŠFï9A¡(©U(£‘hd'–ƒ~SIccc-)¦é|lƒnº³c¬,/ÙØ-——ÿiÎçQÇ(ƒêµqPrUW.«ÃŠ$G…»“ªðÈvq×L0qHÞÅ1ûÑpAmMÄ’Ÿ]io ú ûÿ(p*ey‰R·¸È¼Á" Ú °±±¤¶¶‘‰£ÐÚ$ï2qÕ2qD£µµ_þiÚ{c„Ë@q$r]þ}!‡ ÷—sÐx\ç ÈnJÊyþÆo¼ÞO}$i 'ß ÓÈÜÌÍ› TŠ+i#±± ×Ör‚Õê_ŠÇïÝki™Ÿ÷û£‘XŒ¶kJHIôkÝ<ï÷Ç㡵ÑZE—ÏÏ dRS¡Ò¾ð_楑.Ž”6H‡}^/V©°T•I¬zºÆCqå”—ðü¿ÞðIûmаy±ŠÄ1zKup$¤½üÂáÆFN¸`]Z¢Mœ$q#Ñím’ ‹J' mLÁx<j4Y­tù;ó<‰cº¢B›ÿÒÄñú«+Ž}­Ÿáñx.äçWT$›þ¤ý7H",꘹=BÛ¿RµÆã›ñHŒJléÒDb;&÷wP,BIl›Þß»§`ÇV¬ X`)šø‰ Gñâãÿë äËÿv‡nΊCLëWâ•.à9Šc*]´ÇÌÌÈí…ñq|‹"Çcq'B¥·LÌ$Š„œ8Ol¥D’¸÷GZªŠm3qܱhE¤ê*&ŽÄñrïzC6ÀsCg(,˜™cÊ  ‡wÂGï}¾‘‘p‡©ÄÚôÇ¥…îó­%ÉÛˆó ¼$¬¾I$‚~ê¦ûޝ à~ ˆãpÄÖÆBeã=¬Sž+m¾çŽ/¹Ç¸¼håQ~?%¸ÅÄVŒ©#¶±ÑX›š®R>ŠP”A)qÊ„Ðh’DZ…V^ày«uccSP2Ï"[·•*틯¨úóŸ÷ÄA§ׯÑêø€±_¹"xh‹µZ'é YS#7Îï—Km©ä6¶½±i*‘µÁqZm$&>–Šp©ƒ\êõØØ\¸%‰ãåDèrÊpÜU*¡¢ â€c\Ë`Oû%•Òrv7AþéÔÔÇ7òóýŸ§¡ü÷Î7¥&r|[Ûâ"ÍÎM$˜@Ø—øüžßß6Èq%¬b Y7ë_S\l8–Q#{âHׯ7Ç~u|ðÄO3G‰Re±HÞðÊ;Žÿæãüü¥%¹±‰Ã¿°Ðržçh~•4̰mñ3&Ž-9/..ùÛÚ8¡¶ÖjÝ‘ZYŒ²Ãó–mÄ9FYS÷[‰D¾R­®©þˆÚI““ õMÖx< …ZZ8¾°p¡íÖ‚R•ÚxCòòZ¯µ´°·î–•’çá`ªpc³{PPŽå—øs{—þìgµñ³Ÿý5>8p8ueouÓÎLÃÃ3Srªcr¶¡á|S\܉„Â-mG[Ûµ[J¥œ§ÿyyÝ­--·Z[ZI‚,ŽÐne<Ø­ÔjˆrŠúz*±-.Ö²§~ž×)†‡åícgf8n'ÒhU)¯u/-…ü‰Äc±°0µE,ùC#í?.Çóóbr"-TÑ—Z}lñFº<Ò/ùvâØ¯<4àpj©Ä–‰C©¢ ™ ç®8œ œðh§±Q¥lëö/Q…U<^X”Ú³IÞÛOà„äæóó ©‡œÔAâ™8èˆ#k åñø&W°HC77>73;ûéUVMK=ä³³E…J…RÕÚ*&k«‰ÈŽ, Aã‰dºœ!%ÒSO‡”Y/pìv/ùWûÙϪãéâHÉãU¸Ûédo¯6t:…Òh,Ò ú׌EÚê4ç…'ðÇ8œ &ƒn|nnnvößISL^¯ov¶²P¡Rj[ÏËý’"ô\ÃŒÀ«“Yr²EKRD"‹#!Æv.XŒd¾8ÄWSr@ÈÞXTÈÄ¡W)E‡ÞhÔjå»âMa ǵüæÆÌ,¥Å½Þ™9ß§7<µõù<ÄEiJnB>b0°çA¡ÐéþæoÞx£¢B§“råœZ-MÉ•JqY¼a¥-„^ú/vPt>ýúÿ!ñªÜÍeM'N¼ÙHkŒ E¥ñt¥Éd4þ z€Ñßß×?0ÐÖ××w¦š©C¯`pR]í[ÇÐöŸC„Ü]]^J˜8, ÏìŒ,޹ŸÏãy³1?Ÿç#QIÅ„(n±¸ƒ‰ƒ‰B¡Ò)ÿú¯™8ÞÐë¤\9¯â[‰ÄöE&‘˜õ/Y"Žÿ+¾"çeo—xóMJ<)§•§L§EU?°‘8úß`Þ°1qõF-d%£Ç·ãBNì ^¥GüË´ZAqöœÇãóy½SÞs ¼PQ17®Vkµ¡@BŒmÓB•´ìfk«¡Àh|ÿý~ÿÃ?|ÿý÷*NqRÞcñ¹wcƒú>Ži«Øo£ŽŸýì qüßß&ùÝïr÷Þ}«»¸8¿À`àyƒ¡ÚT}¦ùŒÍf· ö Ùlýý6[ÛÐ@[U•†¡×SôXÄb‘ªª3U<B[Q ð÷ñ oß毄8šÔZ…âìYgÂGm ¼¢¢b|Žçµ†ÏÙÁö¶¼—8å1ηŠ ßg0q|xíý7Þrä‰CÞ½£¥™ã޵žêÅ!þæ¼ âè~«¸¸ ŸÖ ÌÕÍÍLLƒ6{Ÿíâ@Ûà@š8¸B“ét•Édew¾¶¦†‡8¾›î=o´;7p@À«‹98…¢¡ƒÆÞùÏ;33·o¨¹ÁA)ÚÊlÇ#‘„ЂÕo¼Gêøß~ ÓSmÕügRD/oäxCI†üR²,_¨JÄÿõƒßþ6§Õ!–”¨Õ*…îû&“­¿ºººïª½™aï»h·1ìö¼W«ì¥ì{×>ü°¹™²týg¿W*2d‘j¿:R¼Jâ(;¡¥r[¾ÑÄ`q‹8l´$E*¯éô¯éõì¾ÖŸ:]mzoÀF+X”*g7fŽÓ§Y¢gâp6@_C¨ýIP’ ^%ÄDS“Á þ—ç&¾O‡G”Jµ:}‘÷—ÛüÄøÆÆü|q±NYXh0¨ÕXô/‘> †Œû•ŽÒF"ñ»ßå²8´ZA(ªÔëM&ƒ^£ÑL&£þÔÔ½a*:}Úfk¶ÙûäŽþþ«ì}ŸÝdª¬ü¾‚O¢Í¿{·¢¦û]üY<•ÑC¼¥*ðŠQÏÄAURç|$ßȈB¡V/Ghqy»ØT‡øü;ÅÅJey‘$Žq&ž ŽŒA«æyc‘1)ˆžº7H¦ÂJ*ɵÛwÅ1ô;e»xš‰ƒz8ä»TÚ‰»ÝçñGñÖ‡xÉqð*RVV^NÃŒ8Îscx¸ ¿¼‘çM÷î¥ÊråÑéñ„ßÿÙâçç+kNબûESêøÝïrOÅÅgªI ö«&‰ãjßU{ÿÕR]½f·  õµµ±Hãj›Ý~Ƥ×çiòòɺxÚ˜Óé<N(/?Á¨-/+/+߯7ó†å¸à¤NÕJy<ׇóóky®¤ñÞÒvL‡(/Z%_øçßYb⨠±xªì+ùÏmqTŸ‘"Ф8ú®^¥þp&Ž«6Êj°Óò•Ý^U¥×3o䥩dyè™8¡¼„yƒ= ÊÊK Žƒl¸óÁ+Mqk œµ‰Þs‚ÕÚÚÒÖÚÔÔÒÚÚÖÚÖÒBíÈ EAAÖþ‚¿K#çî=‘SPém••“†ûýW¯Úûúl}WߣsW¯êõ´IU—œ[™4‡B¡SÝâyã)ö5LÍgŒF…"_…$ûÀóþ5[õÀÀà`Ÿ­¹™ê ðç°Çæ!ÞèÙÄq€¨gâ õÊdH‰SNÚúomÉú_-—ÅAá`Us³‰ç?¾Câ°þâ£Q£¡0’OÞ›<ÇD"¤ŒAºöwèµGõµ÷«™8ÚìW«› …Ç׈CÕ8ñS #‘ƒC¥ÒsÇ盤-R(Ù‘ÜìWÞ0~/¾wR:Ëq*eeåЀÄÐÀE›­²’çá8ÀÁܸc€ìŽ8HJå&ŽÉY~7‘B—¦¸ÝKåT‡ìN­ª­ØÅf¯¬äxˆã=úþ°LÈn´*rCqÏKÛ¬œmØÓƒôÛ‹/Òt"-`UVšL¶êê~& [Û@_ŸÑ¨Vc‘ê–÷…@¶‹CI!DM—iÃëmhàÒÓßünü!¤V§R‹TG⨮¾Ø/÷z´Ù Ž£K 7ÐõÈz­¯TݹCû{}““´qìnc/Ö¸´˜Ãhü¡µªJNl0mتLÝG³;P} Ù @öcmäyµ6)ï§³çÞRÕ)oHuÕr-»B£ÑJâ¸ØßFò°ÛMUßûžR qÍ2õr8Æ×q$YŽV«Ó)<7yÇç›™™žžžš™û…Sj!ßW>%™„W0ôFÚ³£¯oðj_ÿÀ Ín2½¦S*¹’ ¯¦ŸÆÆ Ž [‰ï‰£€ÄÁñ»â˜ž™kp¦Uã¦å´o,/8ôÍÕ}Lö«4ÓŠÅ&Í÷i{íþܹÄ?Œ•”Q†Q¯/ªn6™x)9~{ü6c|üöÍÂÂd!®4W£Óhäëv;:ì·÷_íïk®nn.’ÆŽ!kj¢ä–8n•”Hsm™9š%qpBMÅŸnËê`â(âÕÉy1ìƒN/‰ãµ×M41·ÙN#¬úûi—YCS‹P\P`8(µVDÌYLÑ›|_ç0›-–ººzÂuÎU_ÏN[,æ:ûÀ®±Ô¥°Xêëêϱ۫wH·ggu–sìŠRsii©ÙÜÞ~åJ㤹WþÒ7aW”ö$¿›ü]{zp'@Ö‰CÆýSGóƒLà8ç:WÇÄa©'qXê,fIŒ¤=,ä‡#i önJWHÞHŠÃ\g6÷¦ Á>²«Ì=ÀY)ŽÞ^‡£´Ôbq‘Î9α`ÂEq‡O¸d9\Jž 8C:ÕqŽNtH:©c‘Ãåºäp¹êêœW:;;YÌÁ¿}}§Ó\çp@=±×ÕÕ_¢Õ§zr„ôA:'­\%?œsP8"»”´ˆEŽ4X¨!‰ƒý¯stt\¿Bâè0—ž”‡Ã\êd—w¼:â7Bã=ã!  ä¨8z{ÛÛi‰©ô$½Qʂ⠒ÃyÉæ’ºz9ê¸ä*•b ­LÕ•J¸º\.·ÛÝ3êîu»\çõN‰+ævs¸{~zò¤ÓÙÙÙátÔ%ÓnùÛæîÁ]ÃÎL€Ü£wï5¿˜XކýAF˜½E"_Eí<Úù*²³#}d¾Ú‰D¥÷_=Ú ÞÞÿ"xŸn}_ú¤àV|c;.WLÅc±ÈêãÁÚõ0û²ta0¸ºöàÁòÃHd¿¶rõÐnŽa“ @ÎFû/ ‡Ã¡p0%¾¦¸Ïþ…¿¸?¦Ë#ì&ÿÉÞ…ƒ$ˆh8oÆo=&y<Ú …W%u0W<`7 †ÃW°³«Ë_­Òw‰FsZbhÿÀt´†r[Ì$Žàýðáà}:Çžø¿ØÓG4üŸ¡°Ÿ)#ÌÞØí>\[ Eã‰ÍøVœyƒ‰#¶dâxð€iƒÉ"ºìg6’E²ºYÉuqˆb¨ëÀM£x¨rX¢4„"‰Ðêr:!ÒF(ú—åµMæ‹(Ý,O$‚áåèÚÃhhç]ïÿbóq"± ×¥¥* 9ÖÖ—C«Ò™µ¿°/DËV¡`Ίc#àxrGX„€‡ E«Ë!é_ê?Ó“ÄòòÆÅ mEâˆF—×Ö–C¾’#’íMYkë’Þ`×.¯ÒBÕŠ8¢G0Wű1zˆ6ÚÛ‘ä’8öêšV¤.ïÑ„ ÔY]ãkI~ÙÙÙél¯~±ø®_ïìX…7·èVc£½=®ŸŽ2!Ãþ%Ki©ÙáXcL«·© ÷úõÐêXÃÑÑñåºq¬†]TƒuòRt9‘ƒUU¡Cµqr5âø|”JdÇ7ÆÜ=¥¥–ŽÎë_>”†µqI®¥z3UغWüÁÍX`Tª©u;ËøÃ‡«¡ûÁ`·™zAƱGþ`ÀÙy½³óÊ—««Ç%KG`MÎq,Z¨µÜroy9UÓµ!й2À*ÐÞq^%qŒKâÐefóerÄ—¤Å¦™œÎnK‡….ûK$cÚèu÷J!Š«¾ctmj­–¤6@÷j8ì_¤>rK]  F.¹\.GwxmýÁÚ_þ¿z掎îàÊJBCòÖOOhsefîúQÞp¢›âu»Sí€f)¶èüDÎRP3ŸÓi¶tt\g'«;¡Qwo»×VÒÑ1þ Dæè–ÆŽE£Ku溳yŒÊzc½Ì—Î…¥ˆ#@ƒ¬œNR?íúé?hOäŠ8FG5@NŠcsÔ--?õŒu¹dqtþE 9äÓÎ+×?ÿä“ë¡H„…noŒc½L ÎŽë×Ö)å-M8,½G¡‹=n…";¢˜`1ŒÛåˆÄ6Ö¬vÑĪËãáàJ,ÑsòäÉv’F޼ß<Ê.€ÜÇj2Ú%þ#¾%Ëbœ’_&ÅñÉC©¥o54z©·—E'´p/†Ü®KçõqY·$qt×—–žd‚ÁX,.ÆC==]]õ¡ÍíkËfóeçõõpx}]ÇÉÚÚé(qô`Z GÅ’½»ÇåYSŸHÞø8´JŽ ··;rªã°ˆÃ9¾ orVr‚#$®Di@È'äë,âX{øñn¢\RG¨Ãár¹Ì ä‰XL ¸\§{%ÜrÔ/Ý÷ý±xB*×rw°äÁõ+N§3†ÃÑÿWüi®õqŒÔ†Ñ §Å!¯T­'VVÂá•U²ÅuÉåÇ—ÉvÀÍñÚtÃ, ÅÝÚb‡ËápŽ®¬È£¬ºeoÔ-Igwbô•C½LŽ`p¼óʧs|5ägß!š{ã+ûµÑ…b*@n‹c]ÞCLD£÷ƒkkrŽcŽº½å“Säc.Wi]½4ü0GÙ¹Rs·4EõɈ# •‡¨¯»{{\]÷Bׯ8:NÖ‡–C+ìórqäHzà(šþ¹.97>*Ï©J…Y“Šª.ï¦Æ£´9lS8(MÑ}Üër,5/~±ß¥çwÅ!&ÄÚÜiìÖeg£}l5DâHä䬪]sŒmàÑÈyqRâEÃ!iÊHgçu 3>IÕW%é ¸+öý÷ƒ¦s©e‰FŽƒõ{EUŸÑù•X$ E»/¹zzºë,æ‹{•­ÿ‰£7w\ ¸Û»Æ°Fx%Ä1–Úü?ÂáðÃäJµq|"e;’#PÄa¶”–ÖËÃpýÝ’8ºåsr‚CÎK7ˆíDVÂÁèGo™‰Ãüåõ‡Ñ¨q¸©á0KŽTh¼·«« ¹nÄ‘&Ždûßr"~¸.GW¤!‡W.ÓØ©@˜–°‚þõµq§™Ä—¤]\”á`Gš7,÷ê’!G4"&≭xð}‚ùäåË,py@s­¤ï(8Éiz0<žÇzR£¡Ïƒ_º“}ãkk,>è¼baæøBGèþêÚ—ÎæKÝbпX_ÚÅÄqòÖ´]ù­:š«n¾—ì¿tŸâ Q|¼ô[¤Yê,rù ZÊ»'Ü£î·tJÌØ±#+l å €8’âX‘‹ªÜŽ¿—žà;¯\é¼NKS¿ì`žè¥}›vE"‘h´;Ùä'¦²È=âaÿ¢|v1ÙÌa1/F"±íͼ?êr°Ë:/¯2V¤­iCa k.a”—””•”gÜÑÙ ¶+Ó:6ˆCBÞ_clô’£´Ôyùrgç•ërî¸ÓYZêc7‰Ç"D4}®ÙCNp|&Ÿ¿Ee¹ÝÒ-êIÛLáàç®:öu;¯¯J0qDØ¥L»â(/ɰc³1vø¦LãxØ ‰qi¤z`Åá(5“7:oÈ»„¯}ÒÞî¨wâ Q”—”67C.³9ÙçWçr-“7¤4F©¥{¯ÜåXÞ’ò"ÌKŽ:vQûb0 ‡>§­g#+‰wz9n†­U­±—_{6ˆCb´—&€¬$>¿Tjq^î¼>þ 9šj¤½£ÎÑbG\¾åf,²6êNæ¼_ÓÇ·r¤8Ѐ8¤§ni‡ùJç•¿HÆãìI~9¼Læˆnm%½!çÈÅ­xl‹…R/y,öx3¾EšØŽE“„ÑeÙ1ñx}]ƒ"ޱèööƒ««_<£8Ö;"_þú#Ð{Èç¹·ŸúIÎ#â xqÈOÝ‹Ôöç¼¾—ß;¾Zíˆb|7ÜÅXœ„AËP‰Dl;y{c!GZÚ:ò(ÞŠ%ÄÍÍh¤¾¾·§gt,z?¸¾þàÁr49§Šèý6âèzÆ@Ü8â3Ÿþ­ªrŒcÈ!âH%Ç£Áû«ëûœñ€´AâØ‰‰bšâ[[‘¤8De°¶YÌAÙ19s* ûc[bbëq$*U^‰±`p}ýp(~¦ˆcãèlõÓ—«VœÏdœ'ú8£<` í©;^=H­R­¥—ãÆvÉ1„,ð wqª ŠIå¶r7yêÅ}ªIæS=ÞãÑev;iKÇõô Ç·GèYËœFŸ±®v%½ ·'€%*xB¡Ðò Ù“[Å®®¯<ŠK]b"™ðއ£÷¥ÝÄã’(èÂÔR–r<Šùý[±D\ΈÒ%_„V×>»8G‹Ã)>[¤Òþô︞œUåa ö=u§°XÌfš‰Û™B:Õá,5wu¹{{zÜnößÝÓÓÛëæ‹\r\rÕÕÕ;\.—£Þᨣëä7Ú££««ÎL·¡K\]u¥õÎÎËfs´ë_òí9EO]sÊç}mQn  %C)‡ˆÃl6_¿²§ gG]©»‹žþÙ“ý(™ƒ‰ã‰ÃQïºä¨«¯ër]b±Ôц~½’`\ìzÚ§ƒnCŸI§/9œLOð ÒͧàiƒÎÇžòyèÈ€ï&÷(íátî7 UwuIÆ`Jè¥îºÝ¹êf3 Fz™/H=r<âv_rѸuG}oòr‰ËqÒ|òä3‹#Ñól‘ÃS–¸0;¾›8ØSüž8d.³ÿ$Ž.—{4)…ƒâ°Èr`oîÞ$L—JO––Ö;Üt»ŽÄápÐWvq¬YåL<›8° ,<»8v3ôäÞÞ±ë çõëÎ+f‹Ëå¦u(){árÑŽÉýÿÌ—]]´±†›¦²÷Òõ®zs)å>hUKŽS,¥æs{i©»çÙÅ!®8ž©¬öÈi…¬TÀs ¤D¥/ïZkKKëµVö^©¸F8¸±!wull\{‡n¥V?‡10>z°1¯çkj£ÄÃÇG7q‡ÀwD¬/{«;‘ÐjwÅá•Ä1Û@â#;¡(‰£¨p°íÖ-’'8¸¼,¯ Ò€çD}-=í+UœÀ eº88~'ÒØ¨T¶^[Z úiè‰C²Æ.‚ 0á8èzé¥>Ç;%ݒÀ\C¬¨fŽ1;;1ᛞ’ÄáõÎÎ)”Jmk«¤qc#!îì$½ÁñjyÅŠiCº`ésÊmÄã¥ÛÅY/PP‚à 9HEÇétsã3Lÿ>1=-VIâ(ªT*UÊÖó©t7G$µL¥&qpoÈ©ò¥%QG§¶6¿€W“86ãröBiB¥Ð)þæ¯ß`æÐé(þ Åªø–ÔÕAâxdµrä.b¢©L«U(Ξóx¨ùoÊÛpŽã*jæÆÕ*­öó ª¤m8Ä–ÖCÑ©÷2®½_ñ† Uè.¾C‹T›ñÍ hA ³e ‡y»LMâ8ëñLJÝã œPSÁÄ!h BÚW\¥É™8 ŒE)q¼÷Þ©7ÔUW½³¸A{uÄcì´œ…£ 9ŽÖÀs …³ÁãñzoÞôxîÜ™™¹1ÊñmƒI¤ú3V"‰D PPÀqÿõ RÇþó‡:%Èç)x‰Ç­‚PR‚ã ¹/-'( 7> stream xÚÿü  & (*- '2'7'11+&32 ('&9&>449-3K,,W\ 6J-M)>k>x$],NG+OkGWTN2F8J,4gwno3n&4UHR.RR8jmMjlk.Rp4hQI MK> >> endobj 38 0 obj << /Font << /F19 14 0 R /F20 17 0 R >> /XObject << /Im4 37 0 R >> /ProcSet [ /PDF /Text /ImageC /ImageI ] >> endobj 46 0 obj << /Length 821 /Filter /FlateDecode >> stream xÚÅVKoÛ0 ¾ûWèh³ª÷ã¸b]×[·5Ø¥è¡iÜ$Xb·Ž³Ç¿iÙ±“&N€† ER$õ}´$Nü8‘†ho)Ó†<.#F CÊ)éO¿_F¼YcŽèNÀ‡t†-)—Ô+kO'§\jGRF½7W¾Dw÷ŒL ÅuTk%ù F•wœ,#®-µÂ©V³ˆn£o›ˆP…2T°ŒÖ1²lŰBî„J%·MuZjj•¬wv>ŠÎ>rO„¤=)e°s¢5‡ˆÌ‘Ñä.¾yN Dœ•U’òxžð¸È“ûÑut1Ú$QVRgégx[Õ\j¥ ¦ò혦M\ƒ`ù¾Ž S!%5ÎÛ¾Ó®³§À¶ ½D^ ­ô\¹ÚØ—ëÒ¹ö²ž.ZíÙÕR“Izu*G¹ó„ ˜ÝW$ƼTÈ-ULé½½ltã$Œ¡Æ;-íÐäЂëšv´*«ÉÆ Yçt—bÇ(“V‘~´W9[§c9=ÛN7úÐiRÆó|’AÓ"Ë'Ðnö]B…vÏÖ Waò—Y˜”`Xç9öh>ÝÝ@‹‡ð e€Öó­uÃ]ˆÃ  åì@Ή õÓãvç“i³ùUõ€ –Õ*À¸~~„Ç"ϳÇúëmLU–ŒeÕ,¨gëq‚>€ã^R«…®ï5ÜÆ w"7˜sÜ‘œÜVº¯eHý™gM=á¡–eâQ ¢Ap1ïšp…RmU‹E6 ó1ÆýäórŽÝ;Ͷý TT}& #êê¾s~8ã„cG>ù¾× ­¢¢“0”³#a8'’ÐOw: ü‘ vÇW€±–ûn…]hê§<´Çß8´ÿÚÚM{/*H¹‚»Åo­œz­E}‹cÁaþØÔÏó(qõ© -jÊ–+ðùŠ$á}§Ì«=ŒQ´gÊÅíM#]nÔÎðLʧчá ZŠ_ÙÇpè4!ÎŒùWP+ —8\™½Ýžö“žQm¬ožx§ájáÞgÆŠcènüú‹³Wå[mN¯¼mœÛõFÐendstream endobj 45 0 obj << /Type /Page /Contents 46 0 R /Resources 44 0 R /MediaBox [0 0 792 612] /Parent 22 0 R /Annots [ 47 0 R ] >> endobj 43 0 obj << /Type /XObject /Subtype /Image /Width 1593 /Height 612 /BitsPerComponent 8 /ColorSpace [/Indexed /DeviceRGB 255 48 0 R] /Length 54166 /Filter /FlateDecode >> stream xÚì½}Py¾/ ñ2·¶î]J‹böNuQîtÇ:xЙáÑÝóTÞj ìÔ©YqâJ‡[°u‹D}X=©z’ªu”’ºVíj•»VÍ(êˆü-ç!>3ÅŽŽâ¤ŽøÊ‘׻ђÍcº;y~/Ý!‰0*¢ø}ú%ݘîþ}úó}•$‚LˆÏYúº0÷é‰Á‘¨(=™ºz,—EIæ·幨¯ãxyN$_1Áš‚P!¦1„ /E®ãéð5ø7j¹&HÂU‹©Òl.ª´TV–“7NþI ^ƒÅkÚtˆöããè8*ƒh÷ )L¾c‚µDÒOñœÙhFS^>áÄD1&VIq‹¹Â¨±0¬†a¨Žg´,£Õ²Z†e5F »Ý¼ÝŒcHwÄ„ ½RIA6" ‡ï`Ü Çâä ! x ˆsxzõWh„&d¥!Åâ¢xu»E£å-ø¡.Ÿ¥¨ùEåobÙFÚYÙ¬a+ÌšŠ÷žÌ X°¤pEœðAÖ!vçàýöæf{³½ ü‚™‡Þ‰‘/†€à…™#m}òL\´+XŽ\ÁðZþO<ç}—}—çùbϳÿü^>ÅÚ‚¶ h{¥YSdÖWY>ÒŽ+â È.¥>¸ßÑ `oênÆ€üqàà]¢<ž‹öšD‡dÕažBSúˆxíªEca–Ó2¥á‹5íN—ZM©N§ÇÓê{§8¿"ÿ=Š-fŠ)š­4‹Ùh.Oz°‹|¢cqñrd îܯBÓe»<×Ù›$ºƒ€à§ûCg"¦‚¿ Û§*пěWÿ\QU©v)c¦XísòÍS<Ϩ*Ÿ)Χܵº¸ü²›ØâM,X·‰Êç ´ŒÑ¢aNùðñ… ©È7M¸sÀÞìp4ÏÃÑhJYl¶÷44":ÏÑíðWL aËR"²C«Ñ°Ð¥qx3`–ÑP;|ïð›´ÜQAòs^ÞÏ9½\;Ÿÿ³bЦŠ}N5Ïkk°”†aµ,«5k,¼6&‰¢(æSAÖÐÆÞž+}ìóÔá¸ht¤æ†/»Íu,1%W”„ÃL˰wØÏAh*6mâhODE¸eBDtðj”ë´VËSÅj :èwxž§ŠŠcXÀ9ZãŽÃS’ô JÀÇ%zƒ +>`·7v÷Wzç‰ãb ;M4öö6€)QédI I¼ã!Z~8¥8xßd.`NËæ8^¯7jXž;*NE«Hüb™J}‰)zÄ[Lžá)ÚYÌkxu1Çr0úJc´°‚Tiº…/´’¯œ`å;c§ûšú€;e²pØ¿ \L5U57õúú8HÜäó8îõ#‘p{´·FÅèɇÿÐmÅÀñœ“Õ˜5þAáÈ)IŠ}v‹ì`Ñ´H AbÑ!ª˜b)Š¢Y'Å0@rPàÆlÙ>%Í)ù׉­Š`ÅqßB{”£éJ s8š€'ÉÀ*¤8-hÅ~":’ˆGä‘\ÀU‘¨0Ù¡aÁÏiµ<ë÷Y(îhm ÍLñÔr"Ø)¢¬HˆÕj5ÏS¬Z@1oDD{?”GÄÖ­DTæèìûbUó T‡ª@à—×Z¦dn"_6ÁÊ#| Í‹‰C–Žîž@¢ê‘.y…ýa‚u ,*bÒÉ(~ø?%k€S ²G”?õw 4Piy-Í»ŽyZÕÜ@ Çq^‡ðð( ¢ÛXæˆï>e0 iÇ)A:åTÓ4ÇQ DÃðÀ^ fŒ„ï:9+Îöbhnnêëííýr/&ŽžÞÞï¸ÃÑèíýwe…½ùIê X¨¨ÂSw;šÌyN¢!}ä˜Ê1ʳ°žÇj}>§Ó¥¦8¦ìÅÛ=P–£& Úí¦;*N¾‡%‡Ù`BËŸ(Å€ö@«{ŠKı·'ÐÍ\$*—`ÍË y4W®ô˜Bò9Äæ¸µÆf–ákájhÄŠ+éz@DpÆp¡à«ÕÛæôz½>Úçô©}j5ãÁ;$ w<î+h”áa8­Ù$ÄIþA ~HIê ¤ˆYZ\Ü'ûÆ{{¯4íkÀs06׎sÜê@È6 ÙÜ!_&Á‚`0@ưÙt[ëme¿µÙ¬`ZS–ëëm%ïÃ¥NëÖr[myYII)øùEI©NW¦+/³ÕÕ¼ÿ1Ü ¼\ÓiµÖZueå[·–o-W ÌéÊKËËJÁ.¥`W°þY;­¿-¯{×Uoý­æo°f3Wiº&Þ0[ÁŠ#¬G9`ºFàËn¼Ô#sDN÷CÞñFÅP…©ÂÑÔ€\“4oí'n‚µ„¹ „ˆ¢®¾ÎÖÙ h0A=æžZÁ(¯+-)ÌkÊÊ­{luuÊf€4¶êt[U€¿[áÜ6Äð_)â @:È!¥¥=tÛÀ1Á{½ÁË¥°¢ºEcÙ^iÔ?Óvœ€`%äøû|ªÿjŒËÐòäè–I¢·ñ÷—éN D€Ü€ùóÙ‡È÷I°Æ Ju2K`þiDžZ;Á\¤ ]©®¤¦ pzJ'xÕ ¹bÛÿ‘T€!¶nÝ ^ükm-`¸BEæø£ÓÕâã×m­í„Ä¡0jµz³É„?Óµxœ¸ VÒP•š¼ãn@t8’¦^—“ ˜e_ÓÅÞù(]$›ƒ`­ñ†¬8jjQÔcQWWƒ8¤³³ÖjÓ¡¹£NË *©¯µvBÞ¨««•5†L[·Uoû”×vÂãüµ|«U·©‘r%%€FJÊmµïïÆÄ^.ו°Z¦ÂXo°¦°Ö¨¬$¼A°‚ˆ¥&Œ;dŽ\nÙ×ôU¯²ðï½é@¡V W°¤'õöär&X[¸éƒF#›ìÏ€þ ¨6q@"©ë´‚±^‡õ`_èÊjë‘:A~N@²j+dk”*PˆX·Ö"KWg Њ)ÝZ^ZÈCW^o-+«Gª¦z«—a*´ÛEÂÔ\ ¡dƒà€ª½²¦t7Ÿƒ³ÿ/JæÈÀWvGSW¯â1w¤TFl¶ÿ8Á“â iáG"QŒNŽ‚ŸÉÑÑÑ©©Ñ¹Ôì!Œ!ŒG„ù#Ä“ä¦ä‘1ð39GGš‹'™AtÐxÅT«*’@HƒàM"ÕH›†Ý¿ÉÀGbìú¨Z™}ÕÔìBÛ€ßû×,ð&øÆ# Èz«T&âp¥_¿qy¬OÛ,. Þ}xcrTL§—§@2uód‘2ƒ§´Édh ÑÆä¤0ÿ‘R Bt¬¥ÂhŒf”·" x#ı>c}uõÎêšäÀùþY”8vîÄÛ@É$žš q¬Z Éc¶2™Ñ݇ÐòÎçˆÏ ‡ÄG'§FŸ€Á~jr2ªdæ%p£¥ñq@@J€x}Dàñ?‚× X“<[‚#)ÛI“£ce5eº2³EoøGPƒsÆÛ=¤ÞÁŠÇÇ¿©©Þuzׇ§?ü¨ZùwíúðE±ëÚLÞØõB+/~|úôåÝm¢änGcÿîx!êõ2q”áë7<…:9ŽG|(¡jê  0Ôcb™‚Ä €õFd÷ÙGú1ï<Ãú#2†äÃäÄ0@0òprr΄&‚Ãp\š,++y¿Æ¢×?…J™ÎRt„`%‰£~wÍG€& UìJv¾qTg Ž»~ó1!‚Eè\íÕÕ»­ç/ ܽè®7"1D ×ðbxß=ÖÂB0†‡‚qź‰KáááþàäŽSä–$$û¾2œˆŒC‘…È0Ú1Dÿàp0„G+”a„ @0ˆBƒÁ‘ÜïSXRRSfÔ·ˆ ƒ9ad"ŽšA˜•*ߺ­vË–oƒƒßÂÄ¿3ÁA´Ñ7h›-ß‚Yð5'L¤ÌA¶‡»{ñe☙…Äqï‡Çxù‡L¦˜I_œ Ï>Jî9;;7˜½Žâ xRbzzúùº!ôÁ"¨þñ9{ºÛQÛ[†´¸C‰Äh¬SˆcxÏçÛ \ªGC{!LúDfª`Þ_ççÛtåå[ ÷⸩«³ét°äÈV¸í¿`9qKŽƒ@½Ô–o+/ý&4„ѹwî˶ª èM/â XªÖ8ÿòΊéW&ŸOýN|÷”–+Šc8dAâ ¨t¥°øùÀäd\ Ùl»ËoLN¢,à7HWl‡Û† O}SÇÆÇ·b"),,ÿ¬™|8•~´ÚJê‘ÊÖm)ÕéjnNM#çx¡ŽÜ=+Oñ¬Ëi˜ýD¢ý×ÛÐXH  B×w§QÈígÈN…xãô%YZàí>C —plî÷?Ì lÀÛ÷ï#â˜FÅ?m³„8–†¥ÀÓe!éjªA6¨¸L¶Ú÷q`2QVÛi·ú¸°zzyÙàä(Úo° Ç–ÏQŒG.ÕÙêlýãX.ŒXëdâØ3„ ‚ñ³à}ÞÞŽzPE™nσ8ÌðèÙl$¤Š`åÇ%´8ðñ|þÅ}ÀÓ3 q`ªøK×ÌÌãïN§äoœþ°K–§‘æøþñc RvíÚYó@=÷î£CíO Žbª"X×çj·ó‹Gèîžz…8ÎÛjjJKÏLâ°Ù=uuÖNx•ÇÏÃÒèºÏQ¥©¸$bbŠDQŒEü¯ãFÆÆÄó2q”õOŽ¡B$1Þ–oC'‡îíÂ’²˜mØG]§$’U+Mçqà'óÄñ1`ŽûáG§Ó*Rýmfæû¿¦§þýEÖ˜[>{4óýék€Ð¸%耟 ·=z³"!‚%áiõ¢°{i|SûâožšmW™ÀׯL»«ËJKK†"ØÏ]SW[ƒGd+jÅ48~‚Œ* Ž!SUdÛÛ…¥@_DÀ"$–PbŽ-7‡'#q¨^‚ƒ!@O%o÷#HÍûßÀtÙTUgÿ¹cˆ\+J¢R÷“–o>N‘w§v*Òâ‹®®ÏNÿHÖ;?ëºôÒ§O?–%ör|wiA^’» øP÷ïKþÊOI„8–„ÐâÊ¡si®% Ž·rÈÄñmá/JÊJt“x°–Ô••ÔƒË<Ä]šÆG‚Ð¥-~ŽÅð8¬_8Á>=‘ñqXÓ*~¾ÎŠóLJŸHHË„‡çl6]aáMŒUø‹Za"¸{ˆÞ Xi☖‰ãö}@{1q|þÛÿZó»;¡“áÎ<ú ‘Å®¿A¿öe|ˆ%^ÚÕõrkì…I„@hÜF‡zp÷^L ÁøuÛy‰ÁÒpiɾŠPõ«z8Ò BıEqœÙ²eKiiÙ$dŽÁÐͺºm¶ÙÄäø·¨‡ß'‡C€8‚ÃÒ%èâx»lxÚ¦"ãX|ÁZæ‚ÕZ^Š˜dxN®Uv9«+»ð %…5Rqæ Xaâ¡ÒÖÛ(t6Œ V-0cü›ššêÉNo€ÌÃÙXdÈ^ŽÇ§ÿ‚*V}ôQuÍÞ0¬8r÷Þ½;èP·ïÞ ËÖ°iBKDçâÄñ¼ÀCçwï¬N§Ú%é 8ZçÈŠ#>ëî)…©Û&GQÄÓ™Bz>ŠŒÁ.ŽÈÈò~H—êpîwº8ƾ3úPõÜø%ÛÖ­åÕrþ¸Räpxòs]YaéD(ð»Ëı»ž\+Mq\S缜7%GÍoÀà?ûC7*_xú;…8>ÃÁS8cæ³”PªG.Éþ]»>{4û#ÌßÌó¯Ønu7†¸ÉvI"ÄA°D\X”7.½ð1žþ8=¸pi ôtI¼a2‰yyˆ8lÈã •á_§ÓMFQÄSÉØ·lz¿ÿǶ/ž¡â–dâ( ŽŒýï±q”9¾e*N" À.ä:"ŽZ 8ä‘È]©kUˆK«L⨪-¿¨%×ÁŠ+¤¬! ǽÀ5‡šâ3$"ºäl™ï>LI×Pbuâx¬8Ò!Ï j%³ÓsÈîõɽûaRe=û”ÁR±¨ãìü&½¨ÂŠz†ûG†·šR%!D#µ……8Bcc£ãÛ`¥‘òÁ ®G%I·qQ’o‡aTÎßr3$Â7X’¤|ëÍ…"0q'KËeæØƒyKÉã°ÅA°òÄxÃ: Çýp؆úotAâÀ-œ¾WˆCúC~õ1öy|„ôÉc\oäÑ¥~Ô˽{·±E"ÄA°T,’Q=ðFý‚ƒ}VÈeÁ‰p~«Õv6ôtðXwí¯—`³¦‡È7^žP*“H³6ÜV÷mdèåVèñ>ÓŒKÑë[Ñ–ƒrþøt N–Õ`ú–›òÛËÄQ_G®‚•&ŽY¥?F8üøñã™Ó§Ÿ÷÷»ÒˆKŒ]Ÿ)•EÒ|’ ß§”*ù~lÒ  CªÎÅ q, DGí¾0ð¦?ÅÆÜœ\['NLNL  ÛÒzTh—*¬Þ$ÅØØ¢ƒm# qHJýܺN«µ(•=#ÁPdìÛ­å`Ómƒ‰ >U¨{"AëP*O¢ÿïqfXnÕ„Õqm„8Vœ8°ÿá,$€é`ãX¤þ¶+Õ%KŒ]Ÿá"‡J*àßÒôGêö3=þè£5ïßþæ“ùP\BKDZ*_í¹éø ‰œœÜHµF#“ý¨°­íÂYÔù(¶ ŽŽŽ}é@w¡_vKR4Ò)G½µ³l½§?‰ Áâ¹ºÒ “ÃHr””œ—BÃÃÁ‘‰©©–Ú-[P ïp|j VÉšÊyäj Xiâ@¾qÛ9 ú8îCßxÍÎN?~¬éJAš7\ Çýa&7R$Çìl99ÀðÝ!‚WÂí˜4¬—B+Ö55Ga}Idlrr°ù*l°ž"…·k`°íñ±Ï!o”ô'GäÉìyL:ئ£ôÌ`h¤p  î TÃ7äèÔ•áQÃSSÓÈ¢Uø 8@|..›ª¶ p\‚&Ž ISÕÝðýûr4îTøÇ™³)‰¨JU*q`Áñ—®TG9\µëôgÈ1†qUw¿‘Ñå"U„8ÂìÀë¹ t€ÊuÅxccŽ ™ªÊt‘ÑÉITã¼°´pZš>ˆ£äÌà ð3öG诽Ž]ãòGÞy ´Ôj;»8 ެݪû­N·gx082<ÚS¸mýJ“¨Ùº-(·1$¥æq¬0qœµÉ9p¨—óÿîß¿wïÞ'55¿Û™ ;|ø=tr<ÆW~—Räð4$“¿«½=ßÖãܶ\.REˆƒàY„l¯˜’÷±!'FUÕn韪à&Ö0cúÌ™3ßÃNâýƒ0wx4"*%t‡Q ÔÈpðÛ3 {öNÿ87žGHÙhd88g Uá3ÝÛ°OšGÜî'ÄAÄñ#æ kg(|ÿö')yã¸Fáo’Ç/ÇÛ~?óÝÀg©yãr@Õ÷?üíõ5°:•ÒÕã~RqˆËMqr×j—’‘ñ†qË„§7T9*U.rˆƒ‘b *™{®Æ 2þËs‘±'ör¥¬•Æð`ÿäØää(Þk0$ üÂ$Êž 8]‘È ¬8¬äº!XaâuÊGlÉ"‡{Q©)\0dwJg¦Ï>Ì@Œ¼ýîû¿@ÕN DP2ÇΚyÉ!+«5$-+qLß¾t¦q…¦gEB!«O/U/K½Ú× £O¡ÞÈÅ™ãp ܃zl”ÍEæ`pn:I ŽO /€Á‰aEUÌ ¼ïddðÆ,ufÏž õ5õ[p—@Tp$>9¦9$QU+Mˆ8.œ³Ùv+í8-ÐÆôQW q¤ÕT—ýæ¡9«?–“9êïÞS:Ðbâ¨?/-'qˆé¥²kw[ÏLúXuøñBõËÔ¹]AÄ«ð4/G•ƒ|……p<ïGAU%{ bx0ƒ!Æ#Á…ˆcx–NO[D¨Ñ'Ñ)Õ.ÙRh«·Ù¶lù¶_þÄÇ‘õŸ³:õõÄ܉£ƒ#à¡cvêäÑ8ªÁ,Š’(¤nŽçb¿–çV¶FYÊÐ-7ã€01o|‚hã^xoMõ|PŽúþt’:NŸ–Û8}/çs̤PË@ø>®_"Ë–§—“8f,ZW}‰\´«Lm,¡ìÔ ó‡j^q _i _ fÇØäH +¤š¦&Æ&3Éš¶oQ¶6reÅ1,wãƒÞë À‰‘ ~ŽaãU(8/KQùùT>Uϲ,£aͬFS¡a+Ì;Þ‹Î X¶ˆÏ·„½yâ˜VòÆ% W9¼;ûèy˜ùî'^{ŒˆãQVÇ.Ži9‰Ãºx}í§äº]%¼±ÜÝ\_ûP J׊àÀýžfG.ÊÆˆ OÆÓoeX¢*2™Ã3ò"^þŽNÂùÈA€Ï’Bó˜}29:y䆼.(¤Œu$˜=»Í™X (&ª”¾)"Ð,øb¸üažó±l±çÁ/» ̾· жˆe ж›-š"³¾²â¨nùÊâ¸ÝiM<ß»w7üÃãçÇ ¬ŸyÐòÍ^§õI,¾œÄQ»äŽ>Y‚;;³‘úÅš$à=+h¶SÎÒR]¹ŒõBÚ€!ŽÁFc£cÏ 2ˆãI°FT®9% B4|25:5õ 6•ñX©LÓÞ¨ŽG¶¢=‚¯¹«ïU3>¡ê#⵫†a9-ÃQ@\°íN'ÍQj§Óéñ¹|ïçññ^1[̰@‹å¡ÑXÌFs‘xòn0?Ñj]Š+é嘺/Ù`TÕ€Lwï…“iólðËÇlxjv^®\J60¿--+q,Þ{úƒç4>pá¹K·gÉ%¾²X´·Æ@vÆÆ‰“A)ŠFp“FÃÐåºòòòÉÉ1Išñž#D‘Äx$Ep åQ(9k0o»?ÇqrJˆÑT±)<&Äat/+`æˆO-ßÝCðÚ{èD¢§ Û§ÌEQñæÕ`ü7sG´Ðù*L ½±¢šcþâKk;{wf–©BåJ#3ÓÏ7^aLÃÜY¼ãL×G2o|“–ÕTõÄñœÖ ;“ s)DØcå°Øù«ÍÒ~¨¢^[õUé5ŒÓYR ™£´ÜZ[Sc³ÕÛê~Qó~­³¼HG½ÆQCÈ0¥:ðGW[Q¦³¢k¿¦¬,YÁ²®V§++Ñðùm7ô‡ÛlÖ­:´Y}M-œÔÙjëm ×PÖÚ©¤8THžFvì€p†ÑÞLq˰ê¾wØMZîÏ‚0ÔÎy½<øåÚùM?/¦¨âbŸSÍóZ¶°‡ì¤aµf™ÓÄ$QÅgý+Iâ¼oræ](8f§Â÷î‡QhÕÌÓáy‚ðÃâÞ™ð½»aÙ¹þèô®]Õ(¢*_^âø‰æÓç_fÇêιÔWäöZŒ÷³ÕG±ËS(:l¦]ÎÒRÔO¼¼N.ºP_Föºº­IÒ@/Ãiè¶–Ö/wÖuvb°ÕÃÍ­µàå2p˜2@¶Î:Äõuu¶¹ˆ²ñù"ÅÄ(JÂaÆ ¨‚áû9üh*6ðí‡OY¶DEñ)|"¹Nkyž¢XžR«éwX^Códz€s4Æ‚3X%b“¥¸²š%Å”üëY“9žþ,~Îà©G/ƒ4æxôøñl8üâïþªÌq{iB…¬Þ4žqŽŸË¾Œq±Å€¶^ÛŽc &ÁÈ0ESÈÅQ^Žv«­9"luVh™Ò•mÛZ[᯵exà×A¿F½­®¬f· *ŽßQ­+)Ó£”–Õ¼ÿ~-Vµuh»Ú÷wC‡Fn«¶í°âÈrõ‘Gr1Š!ŒìÐh  £ÕnÞä÷fø£QIHµbe(ZA±t=T«Yž§ŠÕê|–³à‚ã>ÝŸ•ÀÙER:¸ï§Ç|ÕÌÌsãsËzåþÝðìì3³áû÷_qH¡³/Œ»xÞ`5ñ“¿ad„ã^ÈJ߆pKÎÚB¶e±%jÔ€ç@úßÀC!ír;½Éû=9tp4­fÔÍŸ¢  †žˆ©FÔR¥ayš£yŽ×èõúJe83h'WÊ*€ˆÏmŸ»ð”EËhy­_KóþvŸÏ¤¸ ¦ù¶nÍFòÁ—ødÕGSÅ”º&"hµÛ§Òfʼnc!|ü› ìTjSíª©ùp1T´k—<[#ï—œ¦<3Õ/ß3S<¸tá¬uwÒµû§ZãŒó¬Á@j±³|\Pý¿XŽahúŸè|úßÜ.§?ù"öX"Ÿ§0À²âÃæ!Âó>JË34ÇÚhÁ[ÜOÄDF¡#‚ìV1édŸŸSòù?MÀsåNý]ýáZ Öc—šûp:Ëy?Eç5îvCªÐîñ)®½ÜwÊIÓ´pÍñËpàQ…ÓZðÛ$|׳—8l5é¼Q£‡`~Qâøp~»]øäµ[iã³Ó¡éŸçäþ ŸúnrK¼i(%Gª/eu¶¿€ I7À} Ž¡)¦˜rº]nW›(˜qÆ×?‚TI”Ãd"Ʊ¼Ð£h,ÁÙWŽ‹Ñ£^ )½FcXoF›… ¦´w3\ƒ$$\wGÈ•’M¨‹$kG“˜ç$ºzy É‹Ïj†½>§Ó ¤'K íøÔ Œ˜ðÕ4辎v;ù–fùô{<"8$J d(Ê©Az\qœü®‘£³˜8v§ûa6@‘µÕ‹ÇN(Mv)³©°Ög‰•6^MJ•df:«wŸeyL[‹þ&ÚÍfHðV„ÎbŠns¹œ®Ž"=ruŠ­C„¯ùíhùHÊ“ZLÐ:-Ƽ4ÚÑÖÖÊñ~‹–÷rœQ_4^0˜Ðf‚)œ¦-D=®ŽuÂ=D.”l”Ÿcí1,’+Ohk+X†gxNK»½~ðt NŒ¦THO±Uűuk~UT^ˆ¿qw›KMà ÍÉò–c9ÎÂʯÇWÞɱ8qÔü¬¡j×G5©V«Iº˜× 5Š8‘³7|œ=q!ç'‘Ü ²Ÿ#úµ$YжƒAŠvÒ@q8!qxec’Q¬Õ8ß#“…8_b’ñw¸ÝÇÕ§´þ#œWk1ô¦e »ÝÅ “TÒÝ!ÆÈ)È>£¥(»5ÐiÂ.\‹Ñ¨)ج)`7kýà$?Œ%·GÜˉT²Ëôu㚈s7»]4Pµ Mñ Çú73Û-øð‘cÔ"¤ÂDRmM§?ühׇ»je«vdìªÝ¹ IÈï³ õ¥ &Ó³1ÿ^oþ¼ï&¦*‚¥\8"&Š4°bG;)Š’ƒöº¼§àúŒ:„I ÄÄ©c­ÇÝ·ýÓan³_ëûs¥¾rNT‚¬?ù7qˆ‚x5…@àƒÁCìp8å»)]Ó  qþä©¶?{½œw(êwG¡*‰k}øáÀy}þú—Ê¿ð'RëƯòG°´5ð¾|ŠãÀ䋆ḠKPÄŽv1ë/—;ÍŽææf;ü¹èíí½ØäóÍŽn¸hìÂs†}M}òl7ÜÇÑwtÁyŒ{Ùô{º“8Ç ^ú3:„c^ŒZ†Ég`p=ƒ$´ó˜Sx* ”Ô+MÊùO|àÙ17{‚e}þãžV¿Úçã‹y?ïÓšõ.–mYðq– Ë.#~<³ý×èE<ÇëÛýHfzŽQ'Í d –cM Çù7{)æÃnñèר¯Ÿäñ!})&x¯/&ýo¿ö{|^_‡×{ÄKG1‰1cì8­ÄÁC¯8ËqœìF[ Þ®C' uªô6@±7;šzeÞ¸¼¯±E Ðèp4ÈÄÉÂaï{€õ˜6ì³ë?-(:vN“ûƒ`QøÚ\hÚ²™aŒÐ] c¢/Э~0˜oį˜°Æ8EKÂÑ!í&¶˜ÝÄû<¼o‡úŸù#ZŸÏïä&cÐbÆÞCÚ ®1–³§ÄÕòP;€†ýß7(7]ŠàhØ×‚‰£·ÐÉ•T•£'¸Ò„yãPöýßfC—ê«_2ðéÀîݶ ä™p}"&È‚À·9Ÿ©€Ögð,HQŒŸsµº]JOqŽFBJLBäÓìV­aŠÙjÿavÇŽT»Ï×êöjƪ–T)¹ž²ñEt`LY+ Cc Èõ0ȳš;ì…N«Ø|=)°  ]b‚ † ý\.o›‚öy|j¥¦±¨&Q@3~µ\b@ÓB6Ò˜âê‰Ê¾k‡’Ã~9ðÕ^‡bvjPx£«¹¹©7€¤‡òÉ—híye;$:ºàöÙêÚû1tÉZýÂM<æëZýHî©ui­Q´%´Tå£:EQùœŸc@«ç½vöè\{{ÇÑ£`±pjvKóÅí°M4‡¯õ˜ËsØl2þ ("®€G¾Ù,DT±r°ÃÐ vUUÉÑz=ºÇŽ Áú6eҀ˂á,‡E#޾–|¸¼ºðß‹?=þÐåñ€«¤Õ…Xƒ¦9/Ç_¯× þ9¡ò }ðªhuO"1<›-•&ÑlX5_àÀŽî/»š’tÐØ'óFTÈ÷T†«ÞÅ:…EG œ»›ÕÿÇé »žŠ›–8h ‘»ë‘=ŒpÄ(`‘sƒCÌáõÂruë±V·Çånuµ:Ý>µš¥ø¼–gx¦ýˆVsXsØ F ­E¯7Œ€£ŒšLÈnÑb¨D‡øï6›N³Á€ ÝïÁ+üG²Lz¬3ô8eOp·J×73 µ üaiFëç¸? ’Ñ€ˆGô9%͉ƒŸÚÕÚÖŠøÂ ¸‰ —Ëé䜀=hp!Ñ´^DNÞ3 }È.ú<°¹ìf†-[¥éšôµiõˆÓƒvGËå–ß;d.hÆNð@ Ѓ¸™­,H}zçâÍŽæH/wÖÄÅôLÞ๱M?WÒ]¸4°–º1Š&øÐwm;³H pWk8†Ëgh5çãÕêöbõ?Qj5GqÏk½þS§†N=úóŸ+ ³©R¯1ÚÐ#S· $úÝ ã'Ø!"<² sÁT¡)Ì•ˆ“MJQ0êçkXVFøÅÈýC€f©0¶?&„¡VµÏ ž'Ž!ã$ h§‚<@‚zœ>À´PF1E©NÍ·ÏGÙµ;qlb™íZËv3xbɆ6/ÁÍ{›æ© ¹'€‰£ÛŽ¢§à"ö£È«@Ÿ½9ŽÆ½öµÁÒ¹j[#ÞôŸBw…¯]Ke‡Ñ¢U¡°*¤= xN­åiŠ;¢Õz}§N]Ú^UeÐŒF=,EU9…ì\2C¼Œ¬2%&é"u’’Ñ-š¢–ý]µÜqAœ¯‹¸ÝÇÜÇ\X_¸ümj5íîðD„ÙÈÃSCm´‹æÕ´ˆ@4Åçóêbš(`¿È©ë’ÏDZ¬¶@cÔjõ“ ¿ë5aµ½¤žQëÓµk¸`½4*ØJ½žü‘Ãi ¸É+ŠŠŠ Šô@fŠÌ•SA% +ÉBxñCÞx7oÓ»¿"N¶à¦k½{ÈmÀá°¦kè„ ´†JƒÉ´ÁU9í œÑç9v…Ö=ub㌾uF敌ÿ8žûº_” ¡´¶ºœ@wüœ¥Áó‡73šQž©pÄGùaaÜŠÍS‘85÷ ¡­ÍaW4GO‡S¥˜®äœ?¦HËù[K¼!I³æx¢M¯A\½ÆÜA€´F£ÙhÔh*ÀYS`Lh•n Ux³*ÓI´Ã£è–¿¦¯Z䈓y9‡ÉÅ“%çX~´Ÿ"bâd1Rœ §1Q×’½4¤8ôo<´Ñ½Ç:"±dþ…€ÉCy–%w+ Ç¿jš¦(Þ'JJ°0È{)–¥,U•Æùƒ¯.ܱ§òDª&up L úÒxãÀݵs-Jµ$¾êY3•u­÷ȸFÈ8E:Ìïý¸÷õ†0ºÑåÇÕA~æœÓ·,|÷O¼•£`‚\>ÉÇX8|ãn8öÚ>ÄEVÊ¥ðQüy÷Þ:öw Œ‹€¡TæÐã1ÿêdN×p·µµ¡ý„ô·’£°Eò•;.šf)JÍËþ èÆ¸áqrPΚ5Fc4ž^ qõ ¼?…8S] (o›­ âHª²ZK.ÀÝDr¼0B;×öw$`ãSLnUµTÅLz¤ ½Át-¦Œ·DyÔY¥CÓö"Hä&y#'Ÿ8@ÐÈ|çàýöf;ø×þ‚ÉCï¼®e(ý™^˜Ã3'ä÷kÁQVÂu¹ÍŸ ˆƒLDíÈ\%FÜ0@×ít{ZÕÍq>76c¹Ýï¤XmTXô&!¨Áš¥½uÕU×cG,Su¥)-jªWIÇ!V²“è;kêB^¼Çyr—§ã𝆲1ý…½Ùæ” Myø‰)O¬Êãæ¢5#nÌóFŽª…(ðÁýv»]6m4u;’¦oûƒáåW¢änG§f[E½Ÿ)£lY4àNߢÚ)js2Tiôù€\÷z½J>Û§³Õåò¸Û::¸Sø³æ¨pÚ߆۲xœ(\—R忍ÞÊuþ›“Ù¸A•~òrr É,äh5k6[ôú¢P©ÏÄÕ÷P†åG{Ëi·û¯z’YÝ>Ãá5v5/ÖàüƒNB?i¦ZsŠc®íSÔ…cNàí?Šnïÿ”ÛûÜ2À®OÒÃŽ♃úÿXôˆï¦GNÞ:¿€k$ã8q\¶§zN›\~Ýqc Õ“®ÉA¼¢H ææÆäJSÒQAÚV€UlÃRz£žø¹(~ˆˆÿ ŸÁŸw¸:ü)„H<‚J~$SX"?~ +‡\tl•ÛCñÿ n£B›ªäÙŸiòÍf£ñ«]€Æa\®£'з?-nª'|p4wCæèj´±ÿΚ»žã—)ªk%d‘ªÌü–ÖV–Jl¹øƒ >@Æc2Ä`G¸ÀË]{p`eÜïC¯F ‹K‰¼TâÈé_Ï×ϰZEêÓhJóœö44Zþ§R1uVi„…®'@¸‘hTÏÍa8Mž9)â>ô|Y”ÆsÁƪÜÜv| À&2ÀƒàŸ×°×ìòجrÓfy;U® ½­^¯7ÞW\Iì ÝÑtåË&û|ÐTóï»{ç…¤£ñâ•@ ¯aÿÁ5™TýôÜ‚Äq‰°Åó,zk«Ñ‰(;N• 139lÎ<0„wñgÇiÄñÞúµQÝ9ÐÐÓwùâÞáh4ÚSröº–Ó !(þXTDx|ž‚QleBÃ=­¸,Œ¨Bƒ{“¯7R‚(´˜¦Ð©osÑÿévÃñ>'7áÔ)4fB@”«°BNÜÞÙÖñn VÑK«d©‘‹~ц9¹ïVMÃZpyÅîoºÜÝœ~›R‹Œ8ìMM€”\£Õ8â¡sÕküiúÕ0»`ždíZK“e~H‹–QJB`>QRý’ù?Õ»í½4âÈM¬Ó«çî¡ææÆnXѨ¯Ñ‘$‹‹G†_u/xuÙT‡»¡k†t:ÿEÏj…Q@Z ᪠1u‡f)lxÊgY3daJ ’Ýêq»;°ªÈÍ“$? ^ŒG»c+T.â@#*p1 ÑÇ݈”rsÔý÷ òvx›™n€b©Ò¯£ØÁ-©ôÿûƾÔEÀ)î¬íNñÐYâ_ ÆžU¯¥Ê,ŠãÛ4‰–ná;¦7¥mÕÞ6„XÃð?_è¾ÿUqä|½.¯Ø!Ä ¨)\ ТTKµ÷eÔ£hÂ)öæåµkăéöªD.æ#µ3)ÈOâMDªŸ1šÁÔkB|èju·ºÔ²ªˆÇÁg‹‚ ‰\YE ›T$©cØ>õ H6Uá-TH倒´†žÀãáƒì)!]Ê,\÷ßYExžÞ¶ÍŠgIôdŠàXÐNµ+zÅ ¨ÑŸÐ"×Q5U*ªýi½où˜Ñ >Ã9 àI:qlZ×ÎeHq4]Ìñ•ØÃüÓ*à9`{ ”“¼YòÄdËb|Çh«ÊAž »>Fyhtºà=­/usÁÓê÷´É5ENl{¼Í}Ì­B˹9óf(Ho¸ÝÈ:µAMb¬*W±PaÉäÇÐZŒ5¦ {#޽µï?pðÎú)Ý&ìüà·¤Ûl&BëÏ$>gý‹Ýû›Ò™ãɺ»râ‡R³Ã` ¹z‘£áËÞÞTsxóÿH&Û—³µ("|¹ÕÐÊ¢CbÊÑê˜EÃáq]•ã…'öð8êê'EÝÇœNXQ?–‡_ÍK°Â¡ûXþLª 2'àЩ÷Ümn·''Oõ³wnˆƒú±}*‡çµZK^®J…<êî5),Ãwî}{àBH"Hų!˵뻀pâ‰ãëtâXw%«Âìi^ŒÞùÄbX/Õɱ¿/¥´Ñ2'û塺͉§4'a—4 æJ†ÊÏú!ê„O!™9ÒêvºhµFÙbÿwN»ûØ1Ô%¦~Fäf±ÇÚr6æbæpv¸ÝNÚéÒ$®o”}â ÇY4¦Mrï2¢¬ !ºƒ<ê ënu9Q.P"0 Q©±cÎþóŸQÉ1á–Ë‹ÜT²Òs$oÍCçy¹•1Ä))æ«’9)¥òäæ~•çïÝë#+.7'íØ_ë5•#ŒØ¡·!¥> ¤êíý*Ù;›­š.£Õ)†*{³½;p,XæD8Z㎫íI +Àñ„χËérYpah¾Â…©6@KJ„ëÏÌ>„Ý7X{S.g’›ã£9.gæ¢ÿ†P,®pyåU(8T¤ôÚ+ªž)À>K¾‚E°.S9â‡ì¸äÝ— :ôaâ¸è­V½½Wšö5(¼1ßž£;ìwpY?–I/ÊÙz°Ž“ÊÉWk©‡¢ì¸ž‹K µÛ턘€êðoÀEªàÎ×0 x\÷…5 ›?kãLÖ«‚Ú᡹# §JàBÞbåŠUV§‡#~5‡´ x,ÐÁã O#Xä1m=¦rTÊŒÀ^>=Š Û¤&‡£! ¨Œ>™8ú”ÆqW¾ Ì×?\Þ>‚€x 'GçÀ¾ŒŽÅÎŒ¨4ÛŽå†ÛÝêv%p=*<ð£=s7@ßxdÎŒÁ\ŽV'€Oî϶“ëåÒ SU JZº$ ““J¥Š «ò¤Šéɽý’¸°PÁ?â3&Xë0•#<uöòiÀåÔ{dŽèr`ß8 ŽYz(ÕFM]_B®IÖ_ß¿¼nŽ¸Î¹$ÎÑÅr …Æql‰úÕQ5²SÁ.±.§Wæ€ h؇¸r˜­„¥„ªýX+ µ§Í?&OÅÆ\Ì0—·è oåa[•œ·»€ä¬Ò˜ª«9¤KÀ² b¯"X7×ÝýŸ/4âØ‹œà—v9’ ,õ6:ÑÕ*8€A©)ùöC˪8äbèÒ5=í*ƹ¹rYu„›4;!âp;ãr\IŒŸú»œž£P<lÖÚÊ«)>P±Å a& “A R $§˜Ñ=âP² v«éÝer®’[û%1°pÇ@’G° Ö]*ÇÁÔÜ‹DÝv&䇅rÑÜåËI‚äF 0¯>dæXÖ˜\Üwé-Ñèkõ©U8UŽP>UPND.§‡—K¢½ÑîD\Îê—P~à[•–\§}”ìÏÉÞimÃQU9&³$hä¢ëRìÚV5P§¬Î³z#7rçÈý²TÕ‹t)'ÌA°Ö[*G,µ µsDˆŽý°ÌÆåÞ öîsØÜ@,ÓcŸïEÚÜ|`ý‡q‘s•…sÑ4Gõ Øø„k†ä»]­.\¹Ðå’;¾n’pñu¼oÞ\\q‡r¯Vóïð¹²Í+7çç­`OD69*ð±ã±K%W.Í+êæ¬ÎÓº‰Xª^—!Žsä«!xë-•ã`JYÃææ}{¿ÄîŒ@·œÈ¬U¼ÑÛ´¯)Å’•–p¾ŒþqèC÷/7È3hXW)>ø/ÅFÁÁÞéO¶{Í“Lˆ\qÈ;ˆË9âÞBÉ`¦ÕÝæjS)…EÀAßJqŽCâÈMYZ]È(ž³[,ƒä8¿sbÏV[ã·\Ønov ¤¥o@»TÃ3JC!ŽÇÞ+Êfóq¹xòê’ãšÜÇñšÜ6§S(–T7ßPFyn®Ò•¾äjkWáàÝäjÔ6OÕárçÉ”‚{ûÉ’SìV³Œ.ÃÀ­IC¢Bî- ßüø1¿ÜðiU:ÇßM¿ˆß%·õ2jÒǃ`¬¯TŽƒvGÓÞ†††F»RÁ0)0úúžQ § Wv“gT¸jj|eÉa4à©ÜóµåCs*Tª¯}—Õve£õ\î ˜a䕘Nà_§Ë%³Žò*NâàÛ(޳ó”TClã|®$¨ÕJÏñUxV'3ǹ­—„i!‚Df*ÇšNú‰í·w£Ü@_CJç¿ç!I!Jù|…«Ë^ùCUáIžÜü; ‘=Úx\—½¨,ÚÌåÉ•En.–r ®J ÓýT9ó¯â^Åê|Ž­ªœ“ Ž)ñ†/• i A³Ç{¤ òrQÇNBKºëÖt*ÇAûeXëü¾ìÆ•ª”8ÜAZs'¸ï¾‹¦eòrˆ9Êp/ÿUø¶óË‘s6d–*ÇÅoØìO¹¹*y§Ov€'©G•“ÇS«×U ²pQN|n2PÌÅ´ Ïj†ŸVV!X*B»3ëH)'‚…°ŽR9âz¾êýJ¶;."s•£éÅyã²Ý‘ê q4¹Òô*¹ Ñ8´o]”ÞàŠyI…ýá9y%)(sJ.ö™ƒ œ¹9ó?IA’ÑyV/Ü ’ _ªÜ¼œâMŒÖlÐõ·$lêBºR.S¢” Ø€º¾µ Kãþ2‡d/#Ò™ã<ùFÄúIå¸ÓÐÓÐØ¸· Ûž}-€Žî&ŽÆ}-ÿ£»»¡±É±ì 3;úìöWH¿e§}É`øõ[²g•SWÉŽrØV#G)0•+—ñƒÊ µ| v-BÙã99?ËÉûo¨ ‡ ÍÛÁâÏ7qRZÀS´è¾´)G•·ñ—Ø uí—ÁšàüjôpÌ¥n[úÅoª—¼š‚…±~R9¢ßáh¼"7Ù¸ØØ¼Ï¡Ty¾¡ªéâW½ÈCÒÛs±««Îw9ì¯b«B¤#J×`‡¦ø{šyÅ€#(ŸŠ¢: ' .Xj¤µÕéÂ¥ª\r…\ËíñyÚh§“wú^_ûxTi0 éJñ©ØWL±Z®ÂXi Æ…tŠƒ$_¥Í7~âúÍ#¥ù^?žMòƪ÷pˆ¡‘œÑ×u“Êؘ߯åZ¸ÀåîÆæ4Võu)^rè&‘Ñèh~[•Ÿ'ÞÛ,|ûÌBq@Cô¯Ûï¾qèõx¨«Òk(§“¢½N(-¢#†˜4. 0uÊeq]n§«PK«Çész|jÞÛzdéŒ#>D?’ÉÏß“)žåOù9?·y»© np ¿œho0MÝÂæªë«]Fæ­Çž2/„NR9åub½¤rJÉÞ³÷¼xî³i²ªêÕ: ¸ ŸXd1Ó.h© ¡öŠR\6:£¢mSúp¸[Û }ÊéTÓjím;*%$°‹‹!"1! ±É¿óÞ#^î0ç­¬¬Šã“Æ©µ…Œò†*RÞ@AZ*<)¿üX©ñýöÔÔ½ÀR™:Çûzz.6Èg—R\]ÌXŘQ˺þH@WЭé ÂV±'C;e¸¼^/Mó~g›?Š6‹'{/!ã–t˲ݪÝx3§­4W×å'Å.¬¥³šQOý—än&‘Þ ÷ùB–ýë"•#œ,0…SÆ{—DÞ+] M°iyR¼,¡ŸÓ¯ðà-ÜDù½*0šgUL;oxýh ?¡åPé[)^ry[4íõ¶yÛÚ¼j¯ÓGç‡ðÑZ*ñ'¸~D’®š,¼Ïïå8NËUê+Y*e<0T"š:Þ±vNêMRÞ`aL§ç1Ö’odùŸÅ×E*ÇÌr!K`Ž@ ¯» f¤êe‰C¬2àìækZ écÔÈ0ESÿ¤ŠÃÙÁÛñØ‘¼^ŸŸõ?Þá„yÈGQx¯F{'̦)”ƒÑ^e®ÒðjŽeXŽÓTô·0?ÝšCšãÚLÛ[Å5cµ"õÔ Ô22àw“0íåǺH帓Q µfz9îô6Ø餱¤vN"Ñãñ(²S -a£†a(ÚùO4M»ÜN¯²â—Žæh†axd×Q¶ž4ô$®˜ºà$j±hÞáÁv<§-(Ò}‘n£Qr^^#æªá Á1IîeŒÎŒÊ)»ÉW²üX©0¨Êžj®r475tõî±pDnã¾}ÏðÆÒ›rÌ?÷G‹4ÃÐ4EçC[•ÓŸ|1.o û‘¨€çRöNÈ/KB„çyµ–ÌÁéõúìÌx.E!u¿URO`aÄÄÕ£ø¡„³V»£G¼Zšãh/ÇêõFãœÁˆDEØ`J{×9Ã×z„«îÈÚ{àYÛ: ^ÓÏùý|)¯ë!•ã€]¡ ¨5Ïu]î |x鈪@_OwC£½yŸÂö¥G‹þ&Ú+Ì@è5€8€ÞpR sÄ]E†kˆ^Z !ëÔU~;"Š#Ø|Õb ¢õ4/µ·µµz}þOµœ—ãŒú¢)ð‚É„³ÈM÷Óüq=®ŽuÒ=´Né{¹¤¼!Áxº›Ž7‚ÌT޵JP+45îíêùêåÜLö¸‚Ù£¹ù÷K‹Çdq è ‹QËPÐÇí„ÄÑ&GÍŠaÞÈ·ŽÅñnñ˜(ÅâBÄßávwøÕ§´‡sN­EoÐ? HTˆæ°”£@ÛVlõŸÑ()o¸’˜=g³ÙBYésÎtp|PKÒ8^Ó³ÛÚoiˆÃÞ¸·»çÊ˺ÄËüªïbw ŒÌ]zµ*•¤‹4Z†a8@CÓm´×ÕvJ~1Þcà´ 1þôu´vó¸}ìŸ{µ~­÷D¥Þ4'"/Æú(ë–YÞÜÆo’6äBˆÕÙ÷Ùž1TM“öz°æ»rÄãû÷^ìÃß½Ët¬Ë]K!ì¨¸Ž¸A4ò›™|†£†¢ŽsÒN·3“Czyê~¬‹‰Bo9`†Ëúüí~XuÄǪy-Ïm·á2URÜL×6éïüõ¦7ÝXÁ‰ Õº©ëœ}wg¯höÞ¸MNØëšO到Ûp1ôåàxäîhZºâhos¡iÏ0F3As ãåi§Ç†vËâäàNˆ'iI8:¤ÝľÃ2<ïá};|Å|û¯ÝË Æ ¥SREÅ⊣_>Ñy«ß®“QÞð-R=û â|j¤k–Ù.dòÉc² >½€¸ØÅq¹W‘—©J_ö^VöfÇïï°/™8b‚¬=´ •¯a âà´L>ç§]­îc×#RL”ëˆ$&1ò«Ã”V­aŠÙÅÚìo‡–j÷ùZÛ¼Z£¾òš˜)gÄä¼[àW«\tzê+ˆé,¶‰™Ío­$÷ïõaí§rJÆá:š–h·‚ŽñËrPî+EUÉÃ;bZ©ò9::(ŠÉgü¨õ†ç½ö⣳ííÇO0ÖÂ0|>Kó\;_ÌS¼°†»µíp…ÉøQðÓ¬!IOSK;½»º fDæ’zêoYl e|¶¤‰áëÄšOå8”žÅá°7AöxQ™!7sêéê~&‘ã•ÊãŠF=ø[­TœÌ^/t‘«[=­n,§Þêrñ>5Kùwð<ÏðLû^sx‡ÅérÑÚíz½a%XiBÔbÀ 6"þ#Ï·îä­êDkRO}q.Ó”=.òxq¬þ&†«êní݇r;Ž”4Àý€ö¾”ÞèA•ªRIþ¾JóXÑd×ú5 r°€:òZÍùXµº]­¦(µš¡xŠ×j½þSCO_·ü©Â 7+õ  =T,RÂ`ÀǻƒchÁ¢"Ás¹«ØÑ‘QO='Jná7ˆgâ]?8›5.¦ 3éþ÷z±æS9æû8Ù•‰ÃÑpå%8¡ª§ÑÞœQõ*üJŸ N(‚%GϱjZËÓ„×z½§N i«*MzC¥Q£²—6” ¸/”—‘ñ”¾šËg(ä!wðJšª ‹<[,BéAUVr²^3ÖzWŽgŠÚí=Kñq ¾ËG˜=‚•,ÃjmPŠÝŠå´&Û5l…ÆXi¨4[>Ÿ&Ér…/ö¼÷ q¨6­RGG?©§¾’U?Ë;³ÅEnK5T‘Ì¿7|+®¹TŽpRk8‹£±»'CP¼ ‰zšÒ¬Uû—#hC š l¥^Ï0,›Ï0,£Ù¬ÑVé z 1‚q¥Ö­’ä'Æ^ÆDð^Î3X¥ŽRÞpEßý,qd7a6…Õˆƒãõ_ k<•#®X¨ì- Ý](¨j©)išÃ~h9ˆCD­Æh4š øga L³ÆdÐC³”^K¬Â|Qe:‰vˆ>D2ãš¾ê%Þåê³Ä‘£ZŽŽÉŒÿĹß,~ܹsd‰µj€ðÆ›ÄZOå8€Çù½}/_1 0Ðj¬:¸<Oàÿ!!G ya@|a0YÓƒQ@FC‡Þ€ÜÀ}È8ŸÓ·¼D‘¹ 0GÎ/W_œû{ë¡@VczâȇÂônPµbOqkîfÄÞñ.Ä—¯n˜Â4+ËÐŽ#5âÈø…[U-U1“1 ŽksXhHñ[¢$oƒ¦Âs“6<Ǫ…˜cÓÓUv*3ë©ß$wï›7Õ.ÀÙ•+N\ºD T½!¬ñTŽð—â:6lù‡·Âo¥?ž­ƒø\ôç.ÄW™©ç—9¤žúÊÎgŸ%RÜc=b§rÄ÷77·äШ!qñJà…\âöƆdV ;™x`>Z¬²M£¸ Ç”á¾èw·£—oá^åýQÃ{Pÿ/ÿV Z«V—£#³¼!©§¾2ÈZ[Á½×x*Ç!{s,ˆÞÛw¹W«ú2¸ÜÒx±8܆}Žæ¦nÙz…äŽWÌO¢´ýƒ Y¦¢•-XI´cG¸àK`" bÁáñ!" -Kx«§›bŽUUº*³ž:)o¸B¸ýÛÌ\ò¬G¬ñTŽ;ÍMP`46ÙíöÆÆî‹==/à&Ç[49šަ.Y´ô5€ã4v5.C2>‰xšl›òz ”Üà’FÌø¯dŽÕ“Ñ‘ å ³ÓùçÈW²±Æ»rÄ›It#V 0££©ï…å]8 d/ö“Ã^}ˇ–糉2?©+”2ˆ˜OD…D’´_¢J¸±£#wÕdt\%‚#kð£58HÓúÄOå8¸··k_Fþx׋‡å~Հ܎Æ+_bâ®4½zL•<îÍ™ é5=rÇô¦´­ÚÛ†PöŸá.ƒ3~2o;:⤼a6´<í§ä Y—Xã©áÆÞ¦tÞdpÛLïFÒ`Õ„™£©—d\Ü¿Ù\1ö`´°ê0U*ªýi½Ž¶2Äg8çå1÷îêutd”7Ì#÷íŠâ©ï±î±Ö»rjH¯sؼ_1T— ®º"Ga]8’ÊÑ =$] ÍŽƒËÿ!Åç¬_ËLü— ;:Vsl\+u× ªIóõŽ5žÊqמNŽî$o44-îÛH&Ɇð×n½éÿBBZ&“þÿÏÞ›G7qæy¿U¥%±ˆ6£46éÄÎiäƒ[&ÄŽÉö;÷Ÿdœé™î484\†ž¤çènÈB˜\àÜÛ§Ï‹—I¦msg ˜Ý¦ÀL‚/`l‡÷Œaz†NHfÒÊb Á ’|Ÿ§ª$U•J²lKVUé÷/’÷§žªOýö>ÑZ@“äÐÞPzèX³fÕšAX‡Ä•Ò§rü¿EîAß˯¼:ØñòËçY›ãÃõüq[e¼2­è0+¼'$?)½”ã[861ŽªösïmÚ¼ùÂÙ­þ†#'^YÿöɼAN¯NÈy1BTtH»uÕà·“Š»ì o´• Ž÷˜´Ú–—±ã©…ÅD³¯Ä¯9Pá±~󿣣ýrlº*ïÅèr¸ú©ƒ@Ò“Ò§rL¼Êͨ¢­Š_ÐUë}^«ú~ãÂ_Sþ‹Í›6o¢‹ÆÛ»8yY¯É~9D”„+:„í ûá”$ …—rЃ7ùUíí'Ö¿°>0/^f+;Úÿþ•õçYp¼·š}¹¹³÷ñßÊ9Fdè€~ê ¥ô©þø8²'ÚÛ»~A7+ܼy½ÏAÕâ v´oÙ¼ù,8˜4\dtિ—•÷K^ŽQÚ‚@Ò÷(ï–nâµM›èúo\ÀçK’zÅW@Þõ‹W^l÷Wû­ïbr}Y¸xJùyÖYõÊX­« $èe4p¾‚@Ò‚J9¼}S°úß{¡¹ýÃÍFºý)Uíø5k|´ŸßìoH˜ø·ù×ÇXŒlzmB)G\¼¢CŠŽQJéö0$S)§”£—S„ÿ~¯ýþšŒÍ/ûUØš@HñY›·°©T¯øk7 £c yü»rùˆ†”G Ú‚@•RJ9F8¦“€[7mxùü^­¯Cn{{ Ý9ýíþ"q6ôñ2§xcóæ÷š×Gg`¬tL3y:ÜÐO’ª1•ÃÍóÜ›¼Brü‚Ûí°…í“þÞ¦Í~¿U; Úúèzõ×ÜÂÁÍ/¿¨,n„žÑ!­n§c˜š„“’Š”0•£ßþÞt+›·0ØhfíŠÍ¸ùÛ w3G{Ëf~KÝ ›®*î¨Ë Ðá…ö† t%ûRŽQ{PŽG”›9õë?D[˜÷±Ûªý„¿¾ªÀ£.ÞºŠ”P cú©ƒ@Ò•ÜK9ú‚Çgsz´°éTו/‘j3Þô§M›®*ò°‡ªèÌ/h‚ö† t%ïRŽa±E"!þ«þ¸æÄùMü©€lõÍÇhWÕ&ÎM¯}«Ðãªu•DýÐO’²ä\ÊÑKŠ–³‰|æ·¯mòƒ£óe.8~Ñî‹ÿzý¹Îsííܬª˜Pî—të*ho uË3>¸zõÑú1X‰øJ¾¥âŽú­)&¶ú§9}ÈM²zåe¯ëÏ¿ÇÍ·Ýübûz^“‘õë׿– Øðà@%¹ÖUÐO]žG)÷¢oÕøòØÿ ÐÌü’/å1‡Â†&‚KÌÄÖ×¶ð{Š4oâ€cÓ¯7¿vÕ›H§»ôZWA{C9jwQ/IÚ“‹÷3ÀWWÉ«”#D§%úb™éêý­¯mÚ´ÉGõTûmÚôÚÕ‰D;þâq³<'¡½¡,ÕÈ»¨/_‹[>œêaÑã*9•r„ÊÁ n¨ÞõµWTlzù× F^}mkâQK4ÐAhâ•+ì§í e ŽåG¼±þ`qÄWò)å˜, Ÿ~©÷Û«W·ný¬­[¯~ûïÞ„Ýâq tx5Š“X€#©UÇRºË¥”£/T.a†ž³‘”ZWõ ðí e¢»BpÄ x}‚ãR’þ¿>|xoÍ.¬ÞažÆAŸ®¼t§ñPžÍ¨VãôJ­6¦åj¼-s†ÃÊC“¢ÃÈÉiØw>.÷o#B­7ÚÐ6º ›€×“k—0(èÿèÕ§ü±_êCAÈh8¼ ÿÃÿwíªÁï×\ƒ¾Ø2Ò`yšš\Ûé„j[yš_ÁpXÙH´¢ÃAÛI/ÞF6µèaŽÎ6ÉËqû«O2úð;èMßǾ§^ ÿÙ{#äSrÈFî†g{à¬o¸7«»ØÂߺ¼’“hEGo$Ûˆ »T¶8Ú‰¥aDŠK—ü ðQq#óbWÍáÁìÀ¦Ç®Þ]» s„lÔh#‰dk˜ñO0ÁpXYiDLû7— ù‘l#ÊÖìH ݽvíÒÇ×. \¸v­¿]×>ûìºëó¡¤OûÒÓséRï¥Þ½—z>jj`Íd¡±r=ED&R_>£‘;¡‡Ã’0V¢ ®èЄ5)ËD¤šá6ÉÒ™q{øÖðíÛèõ]œ.5| ½wûýýÿìúgŸ]ûÌuíúµkCˆ)#ÿuÍõ¬™ t'$¦#*oú±ò0Ãa!(.]Nwò¨˜o#,å½5rëÎ0MŠáÛwîà7·nÝ _ߺskY˜ø}ü ÖLòšÌ#¦¯¼éÝ.ÂpX¹JXÑaç6ÉUˆÈÚ _#ƒã6zAfÇðÈÈÐÈç®k.쾸†ßAä _ý,™ÔïÊUÄL¤:4 CumH‹¦®—`yo¨m¤žÙ6*‡N}´ëðß\¸ðÁ {ÊËómûη¶î$ˆ­mmgö¨Ó«5&sV~–ݾÌÞÐÐóÅ%®A#1S#L±º:†ÃÊÀ"åVlj&£½Ò =Wùomm;@eí­­»ß|óч¿?o^Y[GYk[ëÉpï;ÊÊ4ÀšIZyÄ,DåEò#FÃäàB®¶,ŒÒÂð€ÞÙm#"–Xéú®­­ QââÇî7}ôû­Øâ N6µ¶5• Àq ì$¬™„u-ü}âÔ¡Nãµ) ‡U€4á&¥\3³“J÷Tµ¶6½Xÿâ‰6ŒÅÑÚÚrðx3BÊ™ÊÒ8ûa'z k&]  £-ÉfK3†s^SSD:n‡lL¥†9}f“ÉÜçá6Êa·QÈP×!Xbeƒ£éÅ 6l¬om=·ãÍ7±Å¡*k:¾qÃÆ/"ãåÀ6>8ŒÁ¢ÉÍME¶²“ȦDwŒšN–ågägÒ@cªDÝF½»¨õÃo#Üueý/7nØðKL &Æñ€jßú ˜%ÇÑSÈâ8°8(*Y0qR¢rÛBX9ç:­¶søUÛ$ÒB—hCåàÀPü6jѹ™l#Ì÷ÈÄ­E÷Œ«Jÿ°O§]Uer8{!§ìÀ_¾BŠº#~ïg«k £²41_ƒñNˆ·;„½AžPø6Þ|pnCð6ujA5 Bu•eĆ-ˆ»wï.ÛýÐ/™gÖŸ@Ï´GGŽ¿*-Ž[6üò*,äôØ ¯.kjB'sH1rˆÿ¾9¸`…*féE·QÛÔÛHìäXPê¯²Øøåñ&|߀@ñ¿\ßЄÒ®*¬2ÿÿ²/¡Oxý[X>iÉ%r«´F¢¦Ü`Wƒ^<+¦@|8,äà*ÆÞ˜ñ6jk¹QCr•Òäõ{©6¾ˆÃ]襭éåÀ34GÚ.`XÐ/¢?g묡´ ÆÈ°Aß.¦Ehsh`8lbqƒŠôî#Ä6Òƒ·J©^ªj´6µ¾Èz©¶|Hû-ñÓç4üÖ£Ú}8šŽèþ*éÈÌÜÖi¨í@P†®Q,jaiL憂·5­mÔá6ÉÞKõF.ýÃàhXx°q㆗O`kƒ‰yù]U>ý2ÉÚ*௒Œ‚nõÒšZ§§¶ \›ÈϱC®’´Ò> ¿kÎm£œH¶Hžò{©Þøö+&+Ûç¥zåÕdz´6µ518éæÚ¾wîzþãuæë·Âí„$$L¼§vNMŠ KÂrêDü^›PÛ¨­uº÷­e$Ôs(T[7,†¯›°N¿ÈFÉ/ži=ßÔt¾µéüùš.œ¹paOù™= »÷ì)ÿ`Ïî=å{Ы={Æ=ž{¬§kãUpTÄ_ÂB_õÉÖ¨í¤ÐÏp(è¾Àk‚á°°¦»`i• ÿd­…_þ?úúÒ!&j±aË™SíÞ¬í·<·ßjUÜåúÚZgtÆ·5 ]ÜÁ91C0V¡ro£™í£Vá6"¯ÁêÊ^ßú¼T[™;HŽ3l.Õ/ž: 8¶}Š»gz<|ýN`tÄW‚óÔÖÞ%ÚaÝÖ&ˆ•ƒØÉ—ƒ ¯ìm©oJDSo#¼tuƒÀNxÿÔö©—ÏœÇöíÈÄ€ã]Ÿßb+ø«$ g:§uj3NéŸö˜4}ूm4›m’‘þÈæÛnü£ÿ©KìSŸÓ™íÛ…ØØ¶ãw~ïÔÄëொ·ùñÌYð­A6ŒäIÈm4;n`rP°”"6*ñ Ç·ô-{áß°þŒ(7Næcƒy´}kl^ïÆ7 P[ë,Õ¦ã;ºaB÷ø7 ¶öYî¢658«”ê¥ ¤W‰DÅ}‡XŒcÛ¿mÜðÆC·{+¬o„ÔÜ8éPt¹ÑÚÖÄ¿W„Ò ¡rþí¬·Q«`íƒ%–§|¶ÅÆÿ <å+ÜØàçÄûäG/°áóW½¥äs¯Û*Þ]ÛìÏøÖ“|ß×8,²ò5ÉßFQØEm‚m4 ‹,C1f/ÊíK¯ÚðòI_$üÍGþÁoùàØ.Ž]ô׽ʂÂíÏð…PÇœ‹Ò¤N¶Fã”ß ÍßFíÑØGe°”bn.ìÞ«,I~¹ñE?$ð8އ.Çv^”cOP ÇU65÷ pXÍ¥îðNέё‘Wü mê`ÍHü° l#Ùé _Ö¬ÿ™‰7pW*ø±ãÑG19¾Ï‹w¼ß¶í¯E˜8‹ŽWñºSÌ‰Ò ßÚ¤†{EØF³ßF¼i‘y³//ý'ÊxÃ07|^ªWþÖßÿA;Ä|Un%áÆ«,:îy®¾Îz°s¥1nRÝ­3žïe÷´Ò5ÉÛFÓ‰“}8mí0å$÷Õ ðƒ?±äN­ž8ŽÀñÀåS•Žoûý}htläŒd­Ž×!J>GâåœlžŒX•°Û( 8ÎϦ¶‘¬Ä€Ão¸9¸¯}‹ ÇŽ7Y‹ãy„pUù 9>eôÎÃÝðúÝ~[æu@ÇJºÓÈìt€7Œ:q¶QZkŒ¶©†…–“'6¾>áe.íÞ@î:ötpYóò…‡ ·jû¿õò*yý?}~0/ë{ý?aéc®rUN§wU;w2ÕK­d5r ý™8ùÆW·ÂPÀ˜JÃd¡“¼Ó°¢WÛH>ÚnTÆ…à¶8æœ Õs„67¶oßþAXçuz&àÆ7`>Gìt' My†(hŠ Ûhö¥°ä Ž×B”â½ï«ÿÛ½ƒã0Ÿ9h»}ûva6î?nÛ3…³õµW`´SLõ1ì›&ˆþ–û>G!žnm;c¤µ±¬ItžB#,¸B·1Å6"§µšêv¦©ù_ÃÝFÂ˯zB„¨Ï,^ŒÃtÆ_QþÎvÑ„Üw¦ú¹^f,`¤Å㣽v “ÆÞ+ÖØ¼\¤Þ7â3>8$âÇú¤XãœIÊÔ”Ûhz÷ìžÙ"VÛH6Ú®”"PƱƒ­§]U*néøvž›Êÿ(‚QÔ¸*0ÂtÜ"ŒL#ÁKƾÓ:­3¾]üù´²“M'˨@»ÄZ˜æ¤|q·QÛ´¶Ñ9QpP”1‡ÿ5`ÉQo„ QBà;¸1•êü“œ¶m»IÖ”ûjdMrûI€CXQýAkÆÉ†Q·FáV‘uQ$}®î&Nt\iU U”·QçÁûà¹aÓª&ÁÕWÇÅŒ4~J`q0àP!‹ã´˜ÅÁÕ§Qܼ^°7¦´9‚Í(’ 3-ç4ÿŽñ\ ¶ïÒÄmb—XeJt‘3GðÖû0ì6WCœ3Òלܩ€ÅÈ¡Ÿ{¢¸y{à˜N©ᢩE ~éÓùïÔöŸá;~\f¤ãÎ¥¹÷€aNŠUÈmÔʄ„ۨ̿ÚÊŒ$%ºT ³ÀÕ0§"û£ Žßð³ªLáÁªéÈLe†í3¥ÌaÎøœ0>2¬wÂØ"¼I‰ô9„ |åƒ#'ÈbhkCï熸ÿðm£¦©Àz«a®mŽè\µß‚ƒ®ÿ{@¥2²ªBôUÑtdf‚í3_%vÆS¹B?Šé?ãÕ„ídÓI[ˆÎDmjßómÜ‘Ñ\b•)*ÄýGÞ¡ ×0ÛH9½ú༞cõEeÓ\8%^¨Joâ·ÇÝ>åHÇ\ƒƒ?ãñÙÞÖ„^BžñøÓϵµåÄÉ6Ñn¦ÄÚ Á%V™ââm”Ëì¡·Qä‡`™á¼Ž·ãbFâÔ‡û]U8Ä¡âÌŽ=D öñŸ¢èª‚:ý#ÎùXnàŒ%„‘3¾±'H"'u‘Ö àHHpä´‡ÝFÕj¢]YmTpÁ¹ /#ô62:îI63Ñ)~VÕ£¾:ŽyöÓ§à¡w£·yÁb)nq„>ãýQÍ4Eop”£IM¨ý>ë6GbCt±O–çüà°µ…ÜF‹# 8༖£Å1yJ¤Žãìªâ€ã4ÇQŵ=vD3­ 6Ðô£ZTdÁqÿS¹¾3Þwcɉ‚û‚ˆª“çÄÀ1Ž‹q„ÚF;Iß6j ±ÂƒWƒÀñõ)aŒãQ…§O·Ç¸¬þß(î^ÈÊ›B"€Ü<ʶHÎx8|Ž­2‚ÚF‚:Q: HYU6Ñ:R|Q¾OÙIRþÜÝéfU«a®•fa¦ê§NíÙó?ó›`W.¯9Íí«¾Ý ^œ|2ŠÛê€ÂÛ#aÏø´éYî‚>¦èÖð·‰ À‘Hà˜á6šÒâ Cn#7œöñv\ÌDßœâëtÓ™3œùàƒòòÎ³ï„ rüw4÷/t©½8€p½"(ö)œþãšÕVËõ\Aåx(ÂmDú Uÿ6²ù; Ã#Ü6WÜ*:€÷.œ’ã4ÇÒŽíQo:š¶8M†¨¶Ð>_YX°é0m¾q9¼ú†eœÇçÚ¸“œ`v›ò·§Éa™h1±/} «Š~¢MdŒçÚZÃn#p5ÄÙq1½L ±]·¡ã8ëâ)n[Ó0ý°mØýTFqðÛè[Ç“mœ|ý “÷EÚšÂ$àDßF8Û®½Œl£6ÖÔq´³_Óîô¹ÃÉWƒÄÑpU‰kw(‹cœuñÔ>ΦLàÉžñè´5h;™C¨Ã—ü¢ @N[õåïZ RH¨m´Sl¡]3å6j=yò21rNÖ<()õk,¸ü5þ5—'ÑK“¨Å!Zþ‡»UÁÆSÜÑm¹b>ÖmÅÎÑI 4bÜÚ$?ýž{s°AüÎÑK°àŠ”—³Èœ0mÕƒ¶Q.;ÙÏø!/¥;ì6‚ €ŠØ3•@/2ÛH9ºÀmdø&mr<ì ŽóÀ±=TÓ‘wBƒÃ=ÒW”š|ß}ðÎ1Õ$ï"ol‹º‡A5‹œàÛè\¶5‹¬½šïªzÔŸU¥R‘‚uŽ­x1Qé‹Í˽‡a㉡C1ÞF0l\9 ú;½›ã ³q NŒã_E'Ž‹‚côroaê}¬;îOîuŽ­¼Æ(û§syßÏ+œ‚mŠ>lœ>}†-ôÕÿñ\UgBnŸô»¦¼·û·&ßÐý©©=õԖ˰Î1Ö ï %g{Ê—ñ¾LPHÌm„Ûô·Á6‰éô)¾ÅáÇAƒ£.p0¥ãÞ[ìÉ÷c݇^’ïON}è¡¢§h=}Ù cåQÂS>z'<„4Eî<á Hl#(8üähÚýf ŽƒqU5p‹<ÄÍ ôò§ág2Ì`„˜±ð)Žž‹#ö§<ßËvÏTÉ1; ð0€³ŠÙF³‡pA[\eZ§voã·ªRQåplßå`Þä3!pÚÔÀÌxú)¡þ Ö9ærD”Ô6ÁwrÁâ&Ž®ñ=5ãmÔf#ùßê,®’tŠîÈqlq¨8ÝW]<³êmóS*ÜòäS"*‹ctHp½Ÿa£Ü&Á='dT%ö6Ú9CnÀ6R¶‡ƒ A«*•ªœc¼³c»h^Õöm¿M6#f<Zã˜å NUÛLæ6© ðLÃ6ân£x«Úa)\“<‹cÇ›¾hŽKW•¿¯z°Õ±ç©)1޹‘ÐÇd<9ËüIܳ–¶Ñ´Íá6‚ñM Ç›þ€Èà ÅÀÌ)Á1Ž92…î"wzw‹'ƒ¾D4a¡mô!l#OßqÃߎ#LpœgqìÞªðwSqbs¤;Ag¬nÁÍfá­&:á¡)l#$ý4¶ÑIØF  [§øYUÜŽ#aÀÁÕŽwž™Š—½ä˜¹ôÁ'm$•m­'w]-Ðõª”,oŸÙd2‹´ ½£ÙFù¨Ð6¢`%€¾Ç8ŠƒÁ!–Xõ¸ª¤{³ˆÎù²™Ü%¢ÒÕ§e³¦?ˆ(.ñmÔ>³mÜPœÜ_‹Yßg'Ǫ8à8½;téxßTà€•Ž'9õΦжFk[™Ø×7nnŠ5F‚="[‚Tï ivtãBqØF‰iqü†WÇ¡RD~ppLÞ)]U°Ðsw/`?}s]VôýcÛEŠ~… š ÆÆ¨™s¨M“ÓØFxÓ´qŒvü¸í@%úDl£„pU½éóTaräŸâ äEŽ·ÁU%%å!”–³óÀÉ“>oôÉvÚŒ¡>W<ñÞÛc6‹zÅAòR¿†w°{#ßF‘f+ão£Üén#¢ÀñŽ¿É!‚ã8³-äDŽ=OOŽ~Xè¹Ô!"2‘dè y¹A_DõÂ"ËZ½‚£mŸÍ6 '¨W¨Åú'AË~VUþi~{Üè_ÈQˆ³ª`©çR.ã,ÏwñxæH4vp@ÈW“Âãm½8ÄhfqüFØã(ˆÌâø]X‹ãÉÔä‹°Ðs{?Ú]‘Äý ã&î¥f–Y¦1pMˆOå6‚» …^_††¿³8|õDÖ©)ûªÓÑŽ-…!¡ÇÇÞÿ4¬õ\kpæw‹Æ#ñoPé§úD”öPŸüé,¶ÌmR"3¼C[ìÉ÷ b\pP„õ” =®øðØm[ÄüS…¥&ÓÇï»?|®uÏsH5£ó é–¶ "$à®’ã‰_(vÐÃÌvžé6*‡µVœÜ#=Iè’nj¸ªÞôÄÁq3ÛC¶9¬rc!‚Fž9žútÿ$¬ytw&ކ¼ÐÇ*ÈÃa†E–™xY¸OU¸trFÛhÖZaw#} “™Kúý÷Ý?ÿ‡ lCWu‚4s‚ã§þŸ1Ž»„î)ÚÔ@?%¹ðâ(Ü—ÆMwò¨éÝ&æ…+ÖÒ~¬±¬Ô¯Í®‰î6"ó æOIÎ)¯g¤çIÆÀ¯æ§>¹åÉCÁÁqŽ«Êtš;Ã7Ê) ¿-bÝS~C)ÙÞ;ËgM–ë#>ßõåwÃ~/»È×ÃmŒÔ+~àû#ØFêil£QXiqcø£âdzÀ+6æ/|’ŽKlY{†cq¼ ÀÑq\ˆC~K&¹°ç46”„Ü62’»D[ÃTß©GìëÀ]%Ÿ›ˆÑ#a~\£-³ƒ²5ÂB+H²utMOM}r 7Š]´«üàI8僣‰CŽßˆ€cǶíÛ·ÿîíJ4°R‹úà–CZìh°©Ãžõj[C–丟ÜUòH.VAÄÈ{h³ßF ™LÄá¾ûS>ôÔSO 'ƒ?ý—…Y´ëàoÿo~AhΜoqlÿÝ[E -÷ÝçûþÖ-ý^\G’˜ËCœõj[y¤y“#âô)¾Ë+}õ‰¼Þén£´Û(­Òo•'tYON}èÉ)Gõ=e·æ?òðÃz&ÄA¨ÏpLŽÝÛ9ݪ¶oÛþÎÛoâ„[HcËÜoHZw>Þ—g3êé3ŸR«¶¼ò§Æì¿Ów•ôNÑ,\‘Žê‘l£ÆCh©i#–ÙF‡>†h¸2õPê“O>© Ÿ´f™I´/¨3ü‡Û÷Û¢b_•†iÀ*+_Ãâî(”¸Ä³p 3ø”AaõtÑSÓR!Ž‚ÎOm8#pU!t¼ýÛ¢‡XK# #µ°ï,pÂܹÚÅCëv(Õ‘°Ä³p‰Bð€ÂÊûÔtõ~U„¬”ÂÇòãß¾Uô%™hÜwêÓpÏ’` á+w•t%ž…K‚™šJOÍV[z»¨ð¡d_Lãþû ¶@AxB ÜUòÒì²pAŽYéÉTËý÷ûBöÞ!·ìÜUw´Z—¤f… pL[IÇÔÎOö1ÇÁG žàw•|,\€czª…ÑÐ`Œ h€BÞÆ‚Û\jÆa³pAŽHôôS SSY3#)é¾Ô§n4@¾+Rwô®’’â…ëî+К Š`íLã)_Hcá–Ëtq0€C6òö™M&s_ X(wÄ\%£~mˆ,ÜØm _sÓuXþ„GáSO=¹Ð_Úw?„4dzÉ`÷µ1ôI ƒ»JÚêûì·!Á ±Onyè¡Ôûý–Æü¢(í“¥M¥¨P (eÅ# ×Í)44Á.H‹#õþû˜0øý ·\» ®)¹^38¶€&–£Ø »Jº÷âö 9¦“ùú qKazšÖS[ž«gÒ³4ì]†§œUá8éÙ+”» Z­ÇYÑé…;]ñŒ ¥,ˆ„Ð(ï¢aéÏr‹gW‚£"ž'zœ²p5Ó,$#~P4Ö7~à®’ÞC¼záòíÏb¸Qd#7ÿ¾ÏëŸ7¬ÞU’Rüzáò³%(pxƒ@258b쪢IÙU>þsÉñ"þÏ„H$yMsß•ÜU’Q\{á )Âád¢>Á%cN¦@«u‰(νpÍqØ{ hö2͵§ŠVˆnz$´ZŸÛ»†8÷Âí…¼ ÕxtͪÕ.X‰¨_p͘3oQ¸«â­ø÷Â%ç61$W¹V-§µò&¬…$dŽ››9”» B¤suÕŽW.G‚ 4¬‰êæŠå¬JRп37T«õBpW͉­" wN«)¶B8, ±KÅšåÃzÄ_‚;>óÜþtÈ®ŠŸz%Ñ«øh $¢A7–Cœ#þäCÎy×pWÅIqÍÂåJf€C Ö.8ŽÀzÄ]gÑÜ'Cö®wU,ç,\Ž\qIêÉK\OÕòå°ñ– ©…ˆGEˆŒP˜ 8÷k—lX>ÂHè V)pSo Z>˜ââabÀ9¶òâž…f‚“¬•„pW‘ஊñÍ‚º‡ÿ«Á1’£n²aˆ511ŽóÀ1Ë-¥kϸd¨bÀbpWES“fée"LBÛpceLË,xuËÃzÇSÂ~êq¾Õ 宂쪘vñíJ+¸g‚"@ù]K8iOkbŽ5°àq½R û©Çù„wU|ØÜç?mGäÎ WŒ=I7—C!‡d$¸NÇý‚ìªØ.¯¤²p¹—Dr4@3ÕQîe}] ¶ÈrH«’Š„ýÔ¥à qK¬L›ÙKbY¸\Å¿ ½ëúêü„UË¡[•Dd–b¯‡Pî*( ›õ}‚Fº­]†`‚¬¼u—wY_ƒŸÀÏÇ­‡%—ÊÉ*•IÐj=6ê•tu¾ &ÈÊZ®˜Ç®cnÓ€"“]ª»]}I¦ndXƒ ²2?é)Ýk]—†âÝO=Œ wÕ­hd “ÐvDΪ¹#é.€C*&¤[uå-„쪨Úpê…âˆH/Q¹VǾ°{€C ºMH:‘¥‡ŒQï*­–$µZŠ µz“ažZmNO6˜L†ï›Í:v‘‘dJŠÂµWªY¸<¶AÛË=ɲ¼¦#«`Íã$A¤Fj¿ßpŒÜU 8B«5ôµÆ0ßl6% æt‰ (Š$( ÎÂåHЧYg¨œ$@Äd&ø]hV%ƒC%õhdÈbÀéúåþ8eAV>E”J•žžnµ.2›˜YUUUU]]RQQ‘™a@R!”VëõÔÖ)¥c’”³p¹²K6äšRüØxŒÌNeå8¬y|´E`pH1x¥Vë?þ›””ü,ìúR© ‹!rÌ?üa Kš‹3 f„ Ù"ÏÚÃ:"޳·W.]\úa‚¬|Å/²8›2ær”Bý_¼nç5rH 宊¬ð¥—,½Ñd"I“)sqæâ §ÃQRLŒêGIe¡³ºú™g2¬IH‰T`¶Z3gdhµ)º´4’TÂq–z.WétjMS¥sÒôÜËÚk``¼$ì§.Ñ‚«Y®]kI3“L$a2Ù3­™N'âFEõ[{÷:••çÞê½Õ>p¤ƒ#Ãn%µº…€CòY¸\AÛÙêæ\5’º9x|]ãMÈ­Œ›C#—–žé»«¼^Ç’¢ÕªµúyVkEEffEfM¥“²92Ñ‹39™Àqpø †ÂB§#ÓY]RY±÷™’C’Épöì’%ò?Ìý’ÏÂåQÚŽÈUüÇJ芯T û©ß–î¯:}wÕÄ„Çc² phôzla833ip8J'zKá<*ƒáI*tÄ£úŸJ’ &ÓÙ³K—zä~c3"Ê ­dÃÎfh;"SñËÿŽÂ‚(U’ë§FÓvW¥,Ðé蘅ÉéÌÈD²fh’ÐS„J•ô€É`0èÑ¿E把½{+ªJ*2«ª+÷VgVef.2d’¦¾±³³¶vÅJyäI ²p¹‚¶#ʤÊ*UÂ~êïY>Íìª)Z.ä³Zç[39Û3™Ö¤$ôœV¥ÒÏ{ƒC¯OJ_TQQ½÷­ê*G2D½QáÈ4/"I•¦þbgçóµ+VÈû(÷È­Uä(LU8`V†Re–™79„»*D®¿NK³Á<ßj2àÀ·Õj0<’nµâà÷"³{ªJ˜Žªª½ô›ŠŠùéé*‘cug›ÓÒÖ–Ëû(Ûe7K±@’íšASŠã(…‡B%Ñ~êaªP4«;ªæ[i`$cpÌ·¦ô p ¬æEˆgË ŽŠj «ÕJƒƒ‘FýáYŽ"ïþ~ÌkùI#»þÂÐvD¦/OUH²ýÔÃ]SDÝU1‡}Š%s1Ce¥ÕŠÀa®¨¬¬ÀSNgM•ÃQSS½·¢‚ù¸Ã¹Ã%99™ôE„^_WGP©©)) RR,HM±(R/ðRÒ¯J‰© åKÂýÔÃýÖ¢î*±Üª”´Å™ØUYEƒÃ€QY]]Q JGIõ^Gu5z²ÄéÀ­FLI48XEôûë"Õ’’’b±,LM•g÷*¡•Ö+ý3ZÐqNV™È}ÄÏ ˆp$ˆÁ!—¤»Xâ)*3ÓYi·«HÙf3n*‚-Œ’*2?°vUâÎT>Và ,~Í#ŒAƒƒæÅÚ>ËC¥ÊÌ|{Wæÿ¬©®©®p83e GuùÌÜ… ² $ÕKÈ:sEлJ¼ºã$Ôê³g»ºZZ0Ô„ß+… š¤?®Á #tà©€¸åú^ú¿Ã‘žEFé8ªþʯýhêå2›Læ>•fY•¦‚@ "yôSwOÊuW™ÄàMµîl ’îfHúqAñpAc„ ŽÓ>,µ:=}o53¯ÃQâp¦§”N¦àz ”’ü‰)én;¾ÐãQë0'Rl]]99,|q Ÿ¥Aˆ8û|VºÕš™éÀC;Õ%ŽÊù‹´ZÉ9©~ä—òöç$mG@ ÉI`pÊño襦È0Õi0lOøðÀñMñœT|X18œˆÕ™•ŽùÇÜÊmG@ ©©Oœ¦·v݇üxAIj´gÏbƒ£«³¥%/ŸË .F|Ü ³Ùî´.®.Á‚ÕÕNgÆâ¤dFbÕõ=>8~úSÅíÐÁ Ô!ƒ@q¿âÊ©Ÿú,îZ8Ôš³g‘½Aƒ#?-óãÅ5HŽÑ«6ìöŒ :°±woµÃiÏHNR«%V½á‹m(‚co ú©“ LZÑéô•Š šOtuuw#ntu_Ø—KñÌ Š û©Tƒ#33³ÊAÖ”8­Ö j E!kCjЇ°íLâ­åOç4ݯ7©Ty¢¹«£³«óB÷Îç Æå36XŽàö"ªyƒÓ™™IW”;««Kó­úyjtß«pÄCC0A’ò9© Èã‹õ ¶"1 ™ÈfÀhH³5|ÚÐØp°ñƒÃ f³VKúÊÄI½>)‰žÔ±ÈYTUâp⑱UU™ÎLçüd’¤H“1ÅÈÿþÏ.ÓÅ?D.Ć2Á!OIÂYHR‡‚êr¿È€Ã`X„ì ƒ£±¡á`ÇÓà  ÄqJ€Çó­geU‘³²¤ú­ªj<“Ül&)ŠL1y Ë–I Ú‘ à€ ² ”4"Ë~êhÁU*ƒO÷«¨Ø[]Sí,LO·.J6ÌO¶Z-f£Ñl6[‘)b@o ‹¬VkE…£bW‘ÓYQQYYUaµâçÌfܲРŒ–+[ÎgÙøµâŽÀñ£…G--™ÍQÚŽ€@ÒQ±R‹«~¼P¯2Ò)qVàÒ½½…%C²Ù0ßlÖëÓçÌóÑà˜¿(Ç3j0fœ5Ž*+Vm-éY| ·0‘:8¼ ‡Ð0†¶# 0º9Xï‚Ô»Xj˜Rf¾Ê³ÏjµV+BA]‚QUUõOÕØ–À­¦˜±ÕÈ )© ß+©¢G8UTT—8fd…Ì_„³¬l¦þþž-==öeûöuwwt´´de%%3ßÿ¥gIrß¡œÜâx'/‡G-Gò> Ðv©\+é1 ëÆa)b¦¢HFYȤu‚A]…QõVu5Ž’ê½470Bj*hpüú8F ëpš ÉÈ Á¬LÆ>êéYV\‡ÁÑÕÜ’•Ÿt Ž—Hòà¡'žÈpÌÜ”27*(êª÷ \“@b¥QR¡³Üž HR£&¨yj’°WWîÝ»·ÚépT?ó{d‚Ôà±M‹«÷b\<³7É`HR©˜¼*ŠT©HêòõÁÁÑQ·Ûíq»{zÊ˱ÅÑÕÕÕl³%Óþu­öÄ™®®ÚÚeËh‡PüÌâàÐxug‘êÎÖÉÞYeWh,e]áŒ:ƒåˆ„ýÔ4]IâÄ)»}¶0žq”8{«ŸÙ[RS¹«Ò‘‘±woeEEIÍ3†¤$ƒJ5/rhh°ƒÃãÅà8´ÖŽ´ùó1)8꺺ÔæåÓ?lB²à8ËJöàè‡ ² ä]Çò5°±¹/×(=ËQ—’¢ÓêÌf‚"©$¶1URSò‡§7©TF£JEQf‹Ád¶šL*UsWW‡Ñ8|û¦kèÆõ¡±1··W«©«ëÀMuÑ‹1 tºýuøÑóµ»hg{Ê‚¸ý¡Ug’û¡äïU&È‚Ä4¸œ«zX908(åÕU¥Ðä0[(#‰mBu=ÜN´Å*Š Ìf“ÉJƒ£ ‘#%mø†khh]Ÿz{5ªºÚŽ.)F’ÐHº:;öÕ®-Ç79))RG²ÀQmG@ÓÇò+°"1¿‰SfªÊĸdzÐBjŸÈ}Üf³-ÍÉɱØ_š–†­4[îÊ ,FKªÛíõŒŽz=ÉI¦tu=žSSÃ4$q z¼õTK72Dhtt_x<a=èì¨=•íñd+ÈC;ª˜à†ïUg§Üä0³fÁé Q#+ @} ú©Êläàõx,Ôjss0l¶'žx†ÞbpdZZnî… F“%Ëíö pxpt´ã×µuå8R±oßÓE““n÷$’×ãv®Y‘ÛÑ©¡Á®ÞžŸLF½ÍfJI±¨Õ/ö÷étZ:;ËËip¬$©µksãïþqC à8«@pbr…pKIc¾[ýRW|‘cAä„£»û7å6µ¤ª¸ ‹…zi¹ÐÝ=WûÊ{zÜî±1œW…^Oôôì[ÙÙÑÒ˜#K$R$ñLÍèè„ÛíÎ[6èêì¬Cø©;Ñ|Boª¯Çsu44¢j¬ÿþ÷ÿ$âÍF”… F¶#༖7V­H”×sq|ìâÅtó@¿Ëåñ^îÏ}¢££¹™a;’œ¥È®µ8mwAÆnW]lIàþZüyº zתßÿ~*tüô§Á©¸ Õ´‘ªøuwwãûË\gUÌ$lo¨ÜB\wr’Qí²1:ýàè¤Áqèb߇cÄåñ\ëÏÍíìjî X~©ÖÖ¸Ý^ï²Ä‹íÅ€£³³¶­Ÿ6EGgV8b)œëmG$¨»üëtœ£AÅ`rDMJí§¤gŸMJªÝÏØ4+Îá‹ýù®ŽÎvº<·ìðáK$=C¶¿ßãÈË>xhí*<¦Égp0–Eôöb‹ÃãL‘¸®£®nß>-™œüW#‡»80:¸àÀèHlxã‹2›'¼}Z­F§Ñq,ëÀï…EŒ½áv ’dK‹=_—²"777%-f¿¾88‚Ñ‘àÖÁYIhPèº÷_én)¿ñ¤¨èž^éýÔ}6´7eá“iÔßí¢ƒã͇ö]ú³a>cS˜Íî µN­Öø,ÒäÀ] ‹{Ã=18ˆ–ä-3êr—¯ÌµXs#˜ +AÝ,vùÂ/5€1 í’£Œºº«½ƒÍ£âTs „tuuv¯Ü—”ä÷F‘ÃÃýýZ A±&û,óQã(*ÔiL¦^Z“^§·åœÝßÕl±¤¦ÆÌ4ÿ=+1xðÁ‘wRÐvJ9⯠·4r˜\PÈýûpSâܸ¥¤¨Õçº:;|î*_ Gwwù¾¤ä€Y1. püÀsØèP48!¯*®ª‡GbÈž±E‚´Ùºš/´t_À)¸]纻k˜,6›Úh2]bâ×^Ú]åñŽ ]¾LQ*uE¥¾xqdxxdô[§¿¿ËŒm“eËÆé¬áÛfkelÌuÓRðͪ¨9„† 6„à`²º”ŒŽbrHÈäXÜHñ³5ŠÌº&Û‚®®îó47ذxm­ ɈÀ1Dç{½nöoÇ‚PkXTª‹‡9†G18¬KVV¶M–-£g’{oß0›-C®‚‚O¿éèØwœ›Ç´ÀáQ:8úd™Lì{ÖæX¥vŠ¿eC¯"[6XR)¢îDGÇù.xzSw÷… Ý5ôöŽa `làéׇ H²ØÞ?àvôö|Ôß?2<:‰çn £dÜã9\C’×®y½®!{A†ÝãÛÒCQ¸‘IZšFó?æŸ9â‚Ç ±¯S°³J®*)éæà±Õõ.hþ¢lÙ ½aj*¥:x¦£«‹3÷¯›&Ç¡Ã>pLb{A`hÈb% »} ßíî@àè¹Î€ƒž-ŽñRÔKRýýÞqlg p  |ÔCªpèÔŽ¹W¯Ò³;@ ©©'!2RŒú´4ºæïSüw¾««çV:ØÛëeª8Üx’߸{ø†ÉD‘Ì4ñÑI/ýÀôxræ@I\îµXb¨ãdïG¸ÖÁ:Uìãÿ$æù¿àIük ·IéÎVHbšäØù&ÅpÒÓÒÚé&# 8hnttvlèíðN¸1ÆÆ&Ü^÷ð(Π¢ÁáÅà ç^ÚÖðÒàðô Ôµþ{÷,6 YØõ Žº:uìCã1ºæPPÆš±¼w].H!ŽP#þlFRÉ'Qo´\èfÆ6utà)ãøõ¾}¦¤—Ï 59Š‚ þðXØÞÞ‚»}``íᢂ:2S[>"‰¡¡äd’,*Æaó¤û÷­êj¶¤ÍÁŸñÅb؈ bèP8ðSf¬Õþéc°h}f“ÉÜ£ô?ÓhÔéêêü£b»ð¸¾ƒÄåk8› OäðzFGÇÆ²²ØB?J§»=ì¦ÝT^ºqÃ=9Ù»– (Â^ˆ¾†vqá2ÀÇ4j]ì3ª¾ø"`qà÷űï"ñ±¡p€@³T½Ô¦¤ƒ¤!S GG'Í ¦9nǾƒÕßïaÁÃßcw-Y¾N¸jãð /ªëuO0ï­¡‚²ÛÇÆÆ1P&08×éuz$S¹x]¶VC¨ä3¬<“Zýø]4=ÎwuvÖÐ<8<Žàq¹¿··¸˜òuQ'ž®é»84tçWᩈދûûŸ©!ˆ‚,»}tÛ)è#£i³¤ç¢ÕȽ8¸Øˆ|t¼û.€btDо–ÄpÓbQkr§Mfâø‰FƦë÷ƒöUQTÍ38èüªQ\[þoý5OdVj¡ýÆ(S8:L’6›iNÀáp€@±Ñª Ù…Púòx,xi­Ç£3’Ú4[í¶ °³¥%7×¾Ìíu¹ IÒœþLïáÃjµoüA%'?³¶°°èpaQa¡FM½HC×±­1AÃæp=›|nZ~Á‘ûûžýÕ¯„ØøÕ¯Bw‚­y¼Ëƒu‚ä  Ö¾„'shµ6[m­ÝOäæ/›pß*Bà°XjžyºW­ñOûCàØµ £¨˜IƒcÈפdl¬¦ªV;çà< àf Ž•"ä€ ¹_…”€vV6!l5 K©Tµµçp³ÃÜ2Š.°«5kk®æñÞs›S}áqæ†SúB¢ç#¯¢Wš{½Z-þ”9ûCþ7 nÞéôÀÁGœ Ö11p,?:+“ÈàÈÎÆ°XÔ|ùWSûj‘ÅÑ…Á±2— nÜ.(P«‹Š® \»ŒÁ`63öÆÃAí"ˆžüq¦œ½ÑÒŸ7‡ spí G·“œ÷Ýüx½Ð„tEËW9hh0úÑî·¥‘¤Þt¡ñBwwËù.Ür¤—vu·˜Íj•ZSXÌÔkàèÅð0cthµƒv`ÿ€×3K:&˜Œ*\eNQsú‡׸ýêWBt„‡‰pØñÔÒ¾D“I¯W© æùÉ&½á³Y§£ï ´))pe¡óê¨89`lzbƒÃˆÁ¡i¼páÂù ÍÍçè2\<~¡Û\ Qé½áõà¦é48n“Zf–8ãˆb0‚ß¡3°pWDºWîè°ÝŽgÉÆùO‹ÞD׆ÀKŠBà0¨5æùf³Á”d0›M:æ^ÀbȱF¥‰nr0Àð)À”B’?q²ŽåÔÙÑ}¡«¹®.;Ko$Èá7-|ÑqO¢Ï5aƒC¥š§ÿAúä¥éõEߟj鮹£8Â1zÃnǶHÜÿ0!8ðcîÇwÓJ”ü`AJJ>Ís•êó¢ôEV³ù‡™ÕHUHÕÕŽŠ Gfæ#³A…„ï <žÃ‡ç ì_Ar­^³fÍ Òú»Œ‹“#Ñä ‹…$rrêN0àèè¸ÐÝÕUw"?Ëh$pÐMÓ™v‡&îRU½ê?xøá´4ƒž¢˜ì)÷=¯w’žÎ1:ZlÇñɃãÔî“»wŸJ_¾—Ç‚”¬|Ü|ME-2§[­™?t0äx ½r p,6 Ñà 8܇_pD¾Ê.6©TqÝKEÀQŸðØà‚ã§?MÀExvVGª<êp“ÃÎÎÜ\‚JK»Ð¨ÕêLƒ.Ü—ô1Ó7Ö™ŒfóÛo¿ý»=Ho½žFÒò‹[˜>¹cc…Î9ͧŠ¿úU°Åñî¿"¡W￯܃ûÒZ‹O&µ&“}qæb§ÓT]U]“騬t”TW×”dd$!LØ{U`¶Z32ì‹)*%%-MKy@‘qcp¹r§Šß] ŽÄ.!p ýxN§R=‘s°®‹E¤¥5^@àй®x¼ã>p «£°(Å„Á±çí=èÿ®·Ng*18ðÇÇF…Øy% pü++eƒÃh1ê8H“ q#Ó™é(©rTUí­)qT•8JJöV×XppºƒyþüŒ „’Ô¥ØÒ(G„þî¥u¥ò*ä\¥àª í¨JPp`étØù›[W×ѱïP]ÝÙ³ÝÝRTM ††‡N³õN £;«£QE<üð[ï¾û»ßéõ=qôÕí.( KŠDÜ? ,ÄUï¿r¤Lt¤X´ZFÿÀbkEIf¦YN»Óé,tÐr:p|Ÿ É……ècÎjdŒTïu8’ô&ÍÙ³K&D¢›¥J¿?.Gb·;pøÀnIUórWà>¹‡ÖÕ@àh8HRÏÔ`ö“ãvé÷=žÁA£… yY{öüËž==I!‹£±H ì$a±Hä ÒÁáõZ08ÔzµÕŠ9‘Y²«Ò™ét:œ%èaf‰Ã™œ¬¢cTè…2œNU%{KJ’ &€#B5 «”øGÞ] !ŽØH`p`áª>½F¯¿p™ØÒÒp‰¤ÇÀúfâ·^ïÈÈÀ€V›¤Òëw!xd:q5‡ÇsÏóÑ%µZb¾_ñ”HàX¢ÃE˜„u>ŽYdÚgfffX“’ðAV©ô*Ãޠ×?²(ÓQ½·ªº§UUVWgVef.2£»U}cggmíŠÀ…©®¨A€R%þ™^ngeBÏ‹ T@‹«2ŒýR娻·ª a£Â‚Œ‘E‹J£i¬GàX¹ây Cx]É9RèeÕí›ê´.±û ± ŽZZ ´$Ë–™L*D‚òÜææ®æ®óûêÔj­vdG7èÁM´&¼cc=YŒzÙl2iµ×\Æ‘U“IrP(lx<￯`‹Ã«Ó‘”Ùl0X­&Θ²ZÓ ¤ã$\ô’î(q8ì–ª «8p-‡Ã>']¥bƒ„Z×|6-mWqâÝAMëZz\¬!‡bÿÜ›k–¯9’è5)S€Ã›xàÈ^h2QH¹Íg»ºöíS©´Úá‘ÛÃ8ÙÖÃÖ‡ãã=[l:5~ã¥þ~\ àŒ°£Ê¼(# ƒ½}ÄþÈ"«½g¶âи³£²¢ú-dg ~”TðÀAªu"p”#pàqлJüB*VâÓŽÚâ@Ä({¾¶,ÑÐkÅ,©Z­– Ⱥºýûº‚,’´ôõO2i¹Lët·§¿¿ïâ¿]ìéI·àRJv¨￯IêõûêHcAJJêB zë‡_ã¢Ó*VÀŒ¼ÇOÆÅÙº³gñK‚Ãwy°<ÈÜ.+Æ3ÿHÂn/*,*Z» ¿~º¨°ÐdÂ3<,FÙþ™ïs¤¼ƒHQN§Óñ¤I·5ÏÇyS•••5è ~KË`V©´j’Ðâé*¾ùŽô[•ʤ¯¯'H³¹ÐùdFæbÜÃʨ$< Švqº “èà`T—xà Éa³t–þ2æ‚b/,,,zzÙ²¢µEH‘¤JpHSk6ìv9ééŽ*„9Uk\°&AÃf¥ÌŽh‚ƒËŒVÒøØ¿ÿ€$31Ñnµú,Çy&|Aa'c\ Ë‘b­ –3Ô”'H6+½!ç_UW;18H‡ðº#¨ûƒ›+$‘Y³+¥ÊŽ).êGdÛ²³ó?:88ØXÉØ+_ØÿÂþýü¤Ó` ØRº:;žx‚ zû"~Ï#_t yrd:ÕÕ™Žê’ª’E­œT"r›*Hc+DSÍÖH2g $1ŽúÀ‘Ø)8^ Éñ&Çóû_p€d5NõÙpÛ±§Nƒ}Ÿ lœ¦ ú¢t³Õš™éÀuøÅ1ÀBwþ¨ú)·Ÿ®í±—óüìüülL ü?+;40þº½`Oû±u?{ág´ƒŠ±6ŽÄí;$-Ù (B£>{¶³ƒ£¥%?Ÿ ø¥6‰Šv]ˆ2ß`2ÃZM»¨°ƒ ·EÔiÁERõþ"7,­›¡À!ÅÁˆ!/ÝŸ‰ÿñ+~ýzaŸô~ý ú÷õW¬¦Œ 8@’AhuH-Ýùù8ÿ–"Næ-A§ä"xÐø H³ÙnÏȨ®.¡cN§5#é>Àæ:¹ÇïÂJø\U!ÁQ:.p|!€Æÿúüó?þ¹78Ï1;l@ñ‘N§×¨TyâDWWwwGgGg÷…²•Á) 'Ù´[ ~T© éNgff®)¯.©q8­æyzš$04 ÁaøK%”üqµ*$9¤ q鯰ø‚þÿ¿8øÇH9à0êõ*A6Óàè<׉À‘»’NÐÅAqŠ&®‹]Uˆ1*³!ƒiFR]‚ÀaÕë5ŠÐAl4=,$8¤750”ÅñÅ磃}÷3®» ÿ­›÷ܪìÒ¼U¥ÙK– œÐÀX²bÕªçÐÿÒÇV–¾ð³çŸÿÙÏJ[ò8óJK|pe銥¥¥PD·ÿ«¦cpŒ¯ îTòóÁH.ÿƒë–‹ ð]3}pgkòŽçå-ËÏÃΩB&Ê‘—oËΧkÈq59²ÆŽ­ãÃgOÐfƺAü ¯U½ÏNñå¸À’„Å/ì=–ýÜsKWåa+"wÕsyؘÈ+ÅàÈ[š}W˜*´s ÏeãÑFcg¬Y³ eiéÊûö /ülIé’%,˜JüzzÅŠ¥‰ŽqWãºFTöÍX7×DœS"¤~lꋈˆJ$ƒßy—n¯¯ßHýáK»hoU^þmqä/ÍÏF³óvî<>éGˆÁÐø¿þþ c@9ÆÄþܬj²ž‹“AÎá~ÀŠ;8~þó%®ü8 ñ=t‰®áCâ9ü&ï¹U"Ùè%»t¶1§µ„&ƃ®BZ½úoW¯ûùßþís«V”þl?Ç Ï¯\²ü±ÇÖ­^÷wßûÞÊ•/¼°b¥ßâø¹ÒÁq—uÔ—BËÛÙ¬â ÃŽUS8Bv*YÜî£!³#ðuñ.Ýlô]Õ¿øü‹KyÙ—07phÜ–]„=Wx<&âFã•+^6–qäoÀä82HãäØ ïßõœ‚8@ñ‡ïÁÈÍë×]Ÿ]º~ýúððíáÛ·oܾ…ÞÒïÜF/øýá[7nܾþÙÐõk×?»Ž>ûóþËè«®»'FǼ^ºI‚ûöè;ßa}ùÕwC#CCôŽ¿~=¼sgxDü§+ÎÚh„î…Ñb‡ëÊà”1ïŸÏ°úc0ä×- Îλt³ Ók?ÿa#¯8ÿŽ_ʧ³ª±¡rÿý™^dCä<±sÏŸ•ídˆjxÆn«znÞ€$pÐ÷_ž¡!BÇÈ¢ÇgŸ]¾v±á²ëòÐ5×åkè©ËŸ}æBŸ€@1Œ€0rSbhÈívOâÿØä½q}ˆEÇwß}54‚ùÂÆñc¾@ùŸ‘¹Q^œçï>2x$^ûÉ'¬‰áÇŸÊуèå“?Ð1Æ^Úà8îp€¤Ž›øÑê÷<Þ+-)]ÓøåWŒÑðÕ7Ï¿ðÂÊ•]¿v±áèþý+nº®OLx¼®ÁãGÖý|ÕßÕ_¿óå;×]—pÕøÒçnzGo_¿~ç8NÈÝ¿ßu§¾´ô¹+¾¡cåwzð{>¸dÙÍ‘ÐØ’¯\¥ËqÑà ]NËg5þƒ{éfK½}&БcŒýñù}ÈÞÈËßõ…¯wÕ ¯XƒG Ž÷9Üà‘„¡‡ËÅuq8@qU]Y}tõÑ£wãï˶ÿ›¯¾ÂŠ/_ÀàXõqöœb»úε룃GØzóç–<ÞøÕ×w\ÿ{wÓÖ™çqÜ6„Ñ0K!3¶I¢é¤Ò\šc ¥M& D{]©»ŠðËlÝíí\ær/ö¦EJ"ÍNoz1!M<{3é´ÒJIiÝ@œŽ³ªÊ´‡ÝíHÎq¬$Æ/às¼Ïÿylco%icÈï3òf’ÈEþæ9Ïsžg.6Û'9ׄ‘y¬Å">~'ÇýT*îöx[Üq>þ¸—º-9iý(ýîKæžÙw=¾‹Î Úkת䭎ïzŠÛ8V½tÇW…£"#ñÑpÅßôñ‘f»ëÔ—"=ÚÎþŒoÊÝx÷Ý?þ}%|:ýEm¬|# ÂÕŽë†ßÏ‹ IÝÝìuÿžXui¨§§Ë×çr»)ã)]ÏŽ‡Y4úûiˆ"·¹Çi]Õœ6Íoêð'5m:Ú*InIŠ'TE÷ʲÇӧѯ”úß6‡Ãév÷)ɤøÝßy»þÀ^9èiÙVvvüÁe7ZW5¶“oól««^º#•—ªÌŠ™ò8|”ÿî`ÆÃÍ̉˜GûÑ׎¶·Šp Wãþ[åÅ«r†VÍ¡ P%á`#0Gá Dô\C|ÄÑ*¹ÝCìÝx*£Œúi´ÑOcŽ Gv»#ó´ˆ7ÖÇÃIh3’äb݈Ò${&$¼-²Gœ†>ߌ’Ô ~ÛáÛïìpŒížˆöb9Ö¿ܳÅ÷Ýu¾%ßfé+_ºÇ*K™ñ }8–ûî»Gß{}lÕ þ™Öfû¡CÍŸÇbô@qãøì7ß°t|\\O%òñm1–æOV \¨Špä)lÈáùY„£8äïû|C—#—/©É?ËF°"Ÿ½²Ï=´°˜VmVì{;&ö:a5M#ÎìoÑ39Že£;¢ié´t:Þz‹Ÿ¸7žÍ —„0àøQ˜ñ±ÀÚI¦À–÷Ð$+æÕiD5»ýß±â¥;[Ú¨*™7“‘àXPL3géóå½ÓÏíÍÍ,'>ì|Ã\õ ÇÇ|ºã½oÿþþªYŽR•ø–ìTS8’aú(V m\ÄbŠºq_¼ß;¾À3’JÉþ  ¾MÕ/{=ž¡…CQúøÅª¾6¾IŸ¢dtÓ4ÕP0àw«Žx·ä`J'(o;Nç:|£%¡Á~Ò~Di•ý}&àsþm]qR㣑¸šüþ‹¦+^ºKŽŒR1Æ‚cc£ç(ßýßù×Ïž –×Çb±¾CLsó‘æÒæV|‘OóåT4ÐxwÕ„9KGD”#¬TW8TþýÇ4Mëå±ø3kEzJ„㲸£/›Š{e¿ß#G™B>gŒÉ·»çòÂ"…cº¸­:‘cŠ¢gØp"Ë"l‰ª±ùÅ¡nºìOÅT6>qzœìwß;Ï>Å,+ü!Ïþà¿EÅKwq‘­Ø!—u#8*ºñèÛ“'Væ´c±/ÿvüСcÍÍöæpåå§ôx˜jñ1•bõƒ…£8É1a PeáˆóØX˜nêÓ.òr\¦õ¸—Ù‹ýPÏÐeƒîÊðÓv†òh"±TÈ匈,»»z.Íóp(#ÎR7¼s´1‰N] Cž‘Øô|\è~ŠnC×ÞbßK÷q¬w©ÊÇpc«xésãw‹‘G¯œ=Yš}ûòοý–F¥k[âKYžœÒžUï®™å“á$ÂÕŽq~äÒ]#™Ô´û©Ëô?ÄF‹ ÅËý‚¸ |Qm÷ȲìŒ*š¢ fœÖLù‚)]Y5äpMÓ‡™ŒÉg ý~w߬6?Ô;äóùh´¡%¿6ú÷Ú}O,íôÄqRñâ+{’ÖS…;Gf‹Ýx4rvàÔ@8Q¾P»sçΗgN4»Ä¢8ŸC•‹©àÓäÿúÞšpÄ „ª-b›ó´¡'i§) Ç8xBzî7-ÌFZè´&‰‡a©@#O—gTO*JLÕ¦GŠGÎ(åpjˆ…ã(S=½½>ßDJ™Õ´»IŽ=uçøšý+wQ&Å ¨h|™VggÿûÑjâáw*üísUU“™'~É¿¯ãáCq!¬€p@µ…#-ÂašN;M •'Çï‰)Ž…b8?b§«v_W sL–i"\›Sfçb±ÒˆÃy›‡Cli˜öC²øWp@õ„C̳ŸüäÝ9E 3ÄrÜû¼!ËáûØxÃåUD8ò!ލª²Çì´·4ÅÑ7+ÂA/žfÐîOW4•TS‰=ºÉa¹Yüt½PáP+6 2–×ÍÆÚplô‹®ŽÆ·ïüÑ .oØŽp@U…#^ ȱáÝ¢Z\.­©*…ÃÍÃÀÃSâ€,IÒô\LQT­­µ¼¨ê–¸T¥ëš¦…d¯?Üç’ZÝ’ÿÞÂü×IíÁÞ ‡‘Œû»üã¸áï… G¤rOõåågší\½IÂUމÒQ®ZRÑø´ÆÐeZIu¹»»tG_UvÓy±mJln6¦Lý,ÅŒ(}b^œÿ»­EKŒxå`ˆå……ãþÂâÂך–`#‘°?¼k¡FB@ ŽóÃaý—ùX6Êá ÷×ÏÆÆéxȬ„càÝÁ ¾u.T!P á(®²ÇbúòÊ€c±·—Ž"¿¥Ñö!в˜Žtó£Éo©jLÉt¡Êá˜©ìÆŒ(G”.d™¦™Ï+·ù}Žžîž"~Œ“aøÃ»#j<ˆÍC`ÓpäWæÆY8–+±üèéÂ1ÈÆëuá€ç17ÞUï*wÅÜžÈâ"ôP8¤iŽ€Uc‹©û>Éí-7•X´ÍÁÃ1"îáàyh½¥ˆ•U^MO,K…ÙéVÚKÝÁRtŸC. ûûûù{fÕn;’Œy°Ï-lŽdÅÖ¸Êá`Ãå•o7«Ó18papppÝÛxîáHò÷C!×áôÑ#|‹ÃÅ……ûn·äðÓϧ™Ñu=¡'úŠ7ù‰aDki¾#Êç7¤héf)ªë†N-“] G$•J§’ ?Ó\c9rIìvûA»ýÀÁƒU÷ìäÖF‡ùÁ6^ºW4öxÚp<ä¶ÿ»ü˜áˆóª&F½o±xßÊ…ªùˆÏçp„¢l4nꜦTì+ÂçÉù-·ENøàC¤¥MPÈd3jLQⲇ}S÷P2Å$cj‚Î!gáè’ìì«2Ù v…ƒcyõxãë¯Öfã¿¾Úø~¸ÂÕŽ?žô°‘Ÿ ¿¸ ÖQý¹«ËÓâ§iaS\UÊåT¹8ÚpxZdy‚*{K*mlÈÆ|È!{Ô|¢xRù´Çž.Å•¹9M¡«^ :Ã_¹ªªÊ®U¥=mu‹ØjÄÁ§ÇW7̯¾^ޝ¾Þ*)„ª<ü@¿`Ò¼ëu8Y8†"óÅCÃ/vI_e#Žâ’¢BFOޏĕªo4©ë, Å ñâê*>Mî•ÇX8b"-ްªÌjs|Ñ„®WõrÜ\`Ãå°² V½t?alt4üaûm>RúäéÓ‡Ö:]Ú÷‰3ƒíƒƒƒï ʃƒÃc£Âx~áÐfœ’ä|KRXnD"÷‹ÙXL§T-¿\˜ìåž rËz<:gØç̼®Ç”Õ4MÜ3¾THhŠ2ã÷¶ÞO¥Ò UQгáÕŽ ÏdêòàÇ6{ÿ0üá臫>ùd8NŸÝÄ ÍŠËï±| nö8üG€ç%ꔜNÉ¥ëI6~Hß+ß&¾˜º§% …Òu$‘Ó0— ™äº/<Ÿ7YNÔØl¹±Y5¦ÅRôH3ŸÓÚÜ_§ÛÚG"kÇ;Udza8p© ¶ǺNŸ^›ŽÓ§Ç6ÿ–ðà`{ûA8à9†#1á–’S¦±‚¦g7çÓ‹íI5G³ì+ª¢jü ËFÇ4ñËW¦i$t-Q ‡››ËètiK×3ÙÛ##}ã~¿ßãò§iöÔS„CUãñ»?ʪ¦ OïêšÀ <£p¬ªDåuªr86m ðœÃÑ×âq8¥ ¢PD7hõ‡J';Q8ÊøÅpÐe'=±Ú%7­³Ñ…ãAŸËåðB!—[Í-°%’; G:\ú;x"®&wºT.©¦·<#Cݰ>œ¯Ï"ô1Å!\ÌE÷»Ýº»W¢Þ毋ÿðü¡¹ÜRwoïýlùUv1•ÒhÙ,‹ÄRñŲtŸÞr~)¯ÄÄÝ™t>ŸÏ™, …%MOp4Ã!¦óù>—ä¥-]9:s|%ß3擯å²<¹½oÎ'“l°™õËʼnŠ-îãKnÜ;ò;æ+ÐÇ•_ÿwÏì¦pÌ8$wOOï½R6æùÙâ VZä—Êá(æ ³L3´ I&“'ìK…B"±³˜6âpûCÿD"WXœO¥fwŽ–ÇnýBž‹‡Öù>ÿæ#‡ ~7ºÏ"¿ûÝ“áøKÑ•+8þ vG8âÿìpø|½Œ•AGqUU†E€~’ÅÂ4s,jŒî 7 ÚÄ0ó P`c’<Ÿì ‰sCÏ(Z!c˜…܃D‹+ ŽM$b³iö«%Ôâv#ß;-Ýò¥<»Áwn>ÆÃÍðüÂ' vE8ØØa.•^U ѬžÖ3¦±rïBÞ(,%Xx8LŽL&·œËuÅc2º¢– 3Oc‚’y@Û#Î/j ugáØd¶:¹£‘Êc•œ¼6P£ñ$~`à)ÃQJÇÚlÆ_*\¹‚g vC8莄º:åGFÌmðnùMSc]dÄ(«ùÛ<}˜É(s…‚iÒÈdy™汦°_3¥ÆÖÿÝ·¤ît™Sp‡ëjW'Ç%*@8ž‡ú?*-Ã]àëp犷r¤øˆ£¸Ûˆx“§pèiB“塆Y¬‡°`LƒOžgD8æ´ùÔ|ªbjüû…#¾q8<› ì³_×cœ‰âdHÑ€g™Ž„vs8ø–QŒÔ*IC½=CC½â¼ñþÆí“€ß/GoC!o í‰ëñÊ^W‹·E–eO‹×-ñÇ„è1²×ÑÐNê^}V°G{zz$)È¿?Xa{Ps“Ǧת"›|ßV ks*ƒ@8ž GºÑÛÛ³†Ïíp±nP4B"ýA/?<ÖëñzÝ.KxÙ»-.þµþ…Cv8d™v§òxé³Ùåôº}Ž'lóOšÛ$›Ý8±ÃïøaÓ±ö¯FW®”»ñ„vO8Â~J‡Ï·&>Š'øÃ, , ý~¿Ë%âhuHNö¹}/}™Æ¬/~/ßšÊëb_êïgŸa#‡Gv:œÎ‡Ãïlä°É%.ìÕ£"qÀ. 9œŽîb3º{º‡x8²k°¡ G[ù,Éÿ6µÕ$ä§‹P¡þ¿_„Ãå¡Ð°Ïôópx(K;ÇÆ«£|ÆÎÂS`¡ª”Òqå »"¡âÿƒR—Crw•æ7zz»{}>I¢G˜ÏpȲWöxËÙhõzeYP:øÌEˆ}${=’ÃårñÙ q‰‹õÅÍ~e‡?¸ópÉÊqw³ïÊnØOÿýáxZ¦a?P»¯õèõ«W¯±®]¿~íÚ§—ffÄiÙllö³ÏN²Ù,V«Õb±Ù>8s3ªi´*—n4Œ[ÑééÞ´ZŽ9~Š–ñf³¦ñø±Åújkc“ÕútºôúGò…7ÿ®†2f8 Ú±ÏìÍÍ}ïFc}]Ý«¯þéÂä$¥ƒ…ãÓöö'òù5µ£ÃbÝoÿà;Ïj÷ÙX7(ëÏÞÙÙÑÑy®ƒý»þ'ëgŒ¦±d˜"6ô±ÊXëêžÁ1]{c^x‹µQùõoÃx€§n47¿Ógµ<þT ǯ±p˜ùÌ•…£ÎºÿÈ›#çêkY2ذƒFÅp0§:öÕád³¿Ë#›=3bcy&á#5‰€¶±\6òÄÞˆ£œâUœ<°[´¡Øíµµì­¥¦öÂ…«W)““íí›®;¶¯¶ïÜÌÌ´bysÿ~EXlVÞ{kãïY¬7o™5 3›¥tÔÔÑ#ŸýŸ8—Üæç鸏ߛˆÄÕm쨀plÏ‘6=Ø×ÖZÙX¢¦†Âqí?E8lýñ±cµµoR8¦é¤ ‡ETƒzcãï°ïcáøÜ0+Ãauuì1<Ã{Œi>lµ4ütŠùtò“O®_#Ž«W''_Ù_[³¯¾³³4EnX‹Ù`Y(öÃfc#º™:Nv)ŸÏ³Ç™™ÌñãôU<¿{ÐáÃKþ©ÈäÔÔŸP2h’ãêÕO'÷ï¯Ù·¯¶ó”aäø¬=^JÅ>šø¶ò™rö±Í*V`åy8L3“<~Üòƒ\¨€*`·[l­¯þÇ¥ÉÉk<“Sׯ_>r¬±ÑjÕ"´áw45Q-jj~Öðë_¿ü«Ã‡_jõ¨«Ë±Çd t¡*“9EÙÀx`φ〭®µuø¢ǵkS“×o ·½ÑØXWDZÌÃaˆ V,l€QSÓð³W~ýË—nhà“å,ôU:ƉÂqü8]ÈÂ3 °G™Fó¡úúšš£G‡‡éæ¿kW_{Íj=|`*RWW_W5Lf.øÞéæ©Î&ûþ_œ?þ÷ï3çοüŠXe=#–âf³¿=I3!x^ö°j®«ÿ©Ç 𧥏¯6NMQ8Ô»t8Ý NÙÈç;O56þb?ëÆyÖߟ;û«Ã6šÎ°•±”;ÙAW¯ð¬ìñQG}=]‚jo¾tõêà‡ÃÃýëääŬÖÎ¥³Fy™­®›F<ÞØh±üêåó”Ž?¼ÿû†—è¶À›QƒÏ;f±ÚíxFö¼úFº£»}xøê5Öá¿^¢pXl¼I—©(ŽŒn˜3ñÆVëË<díx©Ábe#Ž[ô |áØq‹ÅÞ„çà…@ m~95uíÚÕë7®úéG_X릧³YqJÜÍaÚƒ™™ºúššxéì¹÷ßÿÍIºVeùå[7ió</ ZXÛÄÊ1Å×W]gáù¢Ö"ÂAóbß[ÃÔ,u, çØ¨ãäI ¿G<Ÿÿâó}µ6ܽðb9ÑÖÔTc±YÛ_ÿä“O®_¿>8ÜPCËrõŒØTDÜ×ašÙìÍ3öýµûîojª«»™ž¡[›p‘ àEs¨íçMtkFû{ŸÜ r ÖÔØl ]„£´±ˆ™/dÏÜ|Õ^»oÿA Gäöô4ÝUŽp¼šíÖÕÑ2Ûáá¡ Y-GŽÝºUXËrYB–èÍôt4½uóæ‘WèbUÍ>> stream xÚÿü   & (*- '7'200. ('&6/:'2-/S-M)>k>x6j+#N,NG9cM'IrGVN3F8K+2oo3n&5UHR-QR8jmLk-Qp3gjlQI MK> >> endobj 44 0 obj << /Font << /F19 14 0 R /F20 17 0 R >> /XObject << /Im5 43 0 R >> /ProcSet [ /PDF /Text /ImageC /ImageI ] >> endobj 52 0 obj << /Length 902 /Filter /FlateDecode >> stream xÚÅVÉnÛ0½ë+x”€Šá¾tGÛ´ÑKÑC+ŽQ[Je¥Ëßw†”,Ɖe9†!’3šå½Ñp8aðãD¢½¥LrµÎ$þ i$Ý~yñþ+ð‰êtÈ((QRrI½²öx{ÂqÊ¥v¤dÔ{#ðÍŸÙ·ïŒÌÁÅ»,œJò6Œ*ï8Yg\[j…SÃÉ*;Ï>o-BÊPi@¾×V|‘õ°ŒoÈS¥ä¶NKM­’!³ÓYvòŠ{"<›Ù5‘ÂQ™­9XdŽÌæßò³ÛLäU{Ñ%Ï—Ï›ºø>{—½œm(+©3ޤž5׆Z)D©|:¦åH×°°ü±Ž †K!%5ÎÛTiWÙS`ÛÆZ"?Á­ô\¹ L×!t®½ ;ÃÅpzòvmÈ‹‚$IœÊQî<áÌ>$ƼTÈ-ULéGkÙè^ICvÚ¡È¡&×v”*«ÉV Yçt—bÇ(“V‘ÔÚŸƒÒ!ŸžÝw7û]—7PiRæËz^AÑÁAUÏ¡Ü*¬»ÊU~sëËMÜ\Âçm7-îêk´^ì&0à!$|ÂBÙ %ZS  j˜…ØÚ”Ï´iŸZêîÓ]Î}ò›îAl»M„ñîöY\\5u]]…¯·uM|åP¶yÓÝÄã›»Ëuöǽ¤V 3 \ª5ÜV 3‘{›ô¹î€Oîž»OmƒHýYV}]cS+Š6ñ€ Ñ#¸ZŽE¸ÁÕm×6«U5ûK´û7®OÛ%V¯TàA—2ñ,ÔuÜó½$@ŽøäS­)5DEí'aÊçH´O$!uw< ü‘ ö’ u,^Î$$ZS$ jˆŠÞO”ϑ„iŸHBêî}:@ã_E*êº' ¸oë°Ýl†7†î3´”ÛÐ#µGk;-&ˆ]ŽwÒ†¹Ž3Gµcþ±x˜¼‚û’óÃc>†ŽÛd¨+!‚’+¸Æýý[ŒS¯µÄØ+ ˜bÿ…,Ä,g… Tã* Òöøt¾âÁÖûóñ°îRh£Ú÷Ëó³~õzë vƒí¿^ôBQÒüªO±¿÷&cI3æ@­,ÌK0$Ù7øJϨ6Ö÷Óôq¸Z±˜±âº[½cqò |«Íñ‘uóv§–(endstream endobj 51 0 obj << /Type /Page /Contents 52 0 R /Resources 50 0 R /MediaBox [0 0 792 612] /Parent 22 0 R /Annots [ 53 0 R ] >> endobj 49 0 obj << /Type /XObject /Subtype /Image /Width 1593 /Height 612 /BitsPerComponent 8 /ColorSpace [/Indexed /DeviceRGB 255 54 0 R] /Length 55770 /Filter /FlateDecode >> stream xÚì½mPSi¾/ ¸é[»j(-ŠùÀœTje­¬ëȈžntzßJBjˆœ:ÕD[°mNÁ¾§HÔbð¦ê&$!UÛA>I]oµ]åŒuº•ÖøxKæ«»˜–&µÅWzÅ3h7!›“õÂ}ÞV^ZE”ÏOÈZIÖZÁ¬µžßóû¿Ê2ÅbHK¿Yâ}%2êCg$ùÙäõ³²,‚×$Y’dEQÈ&dûÈoÉÞóýŠ)(((6¢%JCˆäYx/ƒ}ðqÆÚ'ÊbŸµ´Ìb1—YËÊÌfëç}7þ`èÃ;*}†‡hç–3Cǹ_Ä»—†èwLAAA±¡äÆÀðšÅlAK~~Ö?‘Ĉd1ÊŠÕRbÑ[YNϲYœÀ³Ž78CövNo.áô–»Â戓eŠTFE*B‰„B·1Bw#ôû  x»GQæñÚõòÇo·"Iº¾ó3½ p,ËXÕÞ`YÍÖ,M–fÛ6Žã•XoXô,øÝUòl^ÄŒ‘Èô–¤H9Dn7?f¯¯·×Ûëìv¸rüDãmz©RP¼†è!e¨&*Iõ%E²šõÜv§ŒavxÇiAÐú­FËi?ܦåôœËqÙ¹;uäZŒeÖ³bü¸‰‡£ H Ò5¯dÿÞèªÇ€O7ÞQèDAñ2´†Ñb†ø3®[&¡ ‘™©ïº¨ VÏX^à :}«ÓÉè4:§Óéñyslå´ZMVÖ‡V dËrœ^(±ZÌæ\és„hc¼­qõrP¤ î6CŒ¡¢îj»½îD#Õ??÷zàŒÂ^ô7cû”%w^º~¶dw™žÕ°¾ÅÀ±¡å}NA`VtœN“Åè²4nN§Õ‚_@Ú,î9í¶m6ÛÀ›­Vöœhލå‡Hgq)åöq 51š¨¯w4êê`ﮩ?A':/¹•à¯Æ+qÚPÂé‘KãävÍvžc9Í.߯…m¡E”~hå¼÷ò|«n[¦VÃhµ>§N z°ÇJôVÞ‘%€ýkˆÛÇt_»zåÈ1GŒ(5Z1UÁGMO‡£þ8¥ Š¥ÈcA"O k(¢Ÿ³ª`y«Ÿ‡Ð—lÛ&´†ÎFQªFTRDQ‘ Ü` A£åVÇüZÀº æ*–µšwœ”åànW¤_8E*à.Pµ]×@wmœ8º]ŽÅá¨íé© BUE¤p-”¶!Ä"á¿Áœ½…¡B¶%І´!f='ð§ÄÉÜy€ðqL™1„DŠ<ÝâÕi8 §Ó0N­ çµZžåY°g¶r¢\Vz}\´‰~åkÈ $(u½žTصa¿¸’hªª¯û6Ð ß²S_EZ½~ÈRÔíAâoš‘¢Ÿ?øO½9[å/N­Á¢÷‰-çdy&÷;´[x‡–¹Æ›D¤Df¾f9 «ÑèÎ ä«áæ`õ¬Åj˜”#»‰êµUQ¬¹È¾}Lõ×]#̈¤.xÂALUµàÍø‚ýø]ú½QPÄ0MFò…iÌ3âPx—žCT‚À|'Yî,z…Ò* ‘º2±s‘#ˆâN+@vè²8˜çÁî`ùϬAINØŽ‚bmyã„=;Õèé \qÇ·žº[•¼¨!Oé7G±Ùo„uäµ–ä(´WÉrhÒ ã¥ þíŒàoõù¼Ó¢"E)q'[·¢3ê ˆS¡ q/0 ;Xèa!qÃ$Þ•ú7(R!”·Ap´¶§‡H•'Ž&DUAKVaû H±)Ó4"òçQ<†Ÿ#à\±Ç îïz8à#¸Ú<.¿ Æ™0Ãüý ’» RÔb»OšLhÙvN”Ï9u ãå5F'À(,= ɲâí¢¾az (Öœ7ìI^ ;Ô8ˆo¼'Ñ;îè€r$ö‚ýutPlF”ìÆKw+ZÌ{>GòÐíqi^à xÎàó9N†ç¡Ç\iõ± ‰¥V´[¿;*>ÿK‹©=÷øD9âq:º:œ0Œ‚ç­„¶þ0AOÅãvmÔ;ì×zz¾íAÄá¨ë=ÝõIf¬B*˜9¨£ƒb3‚‹Â­4Ôãâç øw–ṵ̂KÐx50ÍÍ~že„èCäÖxÁ?¡`ë²l!Ì÷À£âv»xF#°ŒÛÎq¼ž÷îæÔý%ªõ)Ö˜7ìöEŠ£7¦*µpµ—¤:꺸 ãË¥ØlˆU¿‚X|[Íf}6Çe¶æonkå½#qŸ·š!%òÇD²¯[œ øÝ. £a9†XNo0°k˜8R((Öœ7ìŽä€ÛÈ@'GkÂ@ÞqG}MzèH'uµö;ôK¤Ø l1@ ›_Wå,v~‡pY‘s¾ùº!7›Õy°ýósÍ€5¶ó?ÌøÝ3P•(ª";oȉ<²K8‹²‚/Þ|8¯ÃäómÓð<,+°Ù,ïÝi вÔÖJËR¬=orè®;š7Õ€è¡;æÒÀ¦)"7R$WkëQæ Ø ı‹4Ô0üMúgüØC-µx«ÜsZÓgÕóËrF𦧂b­‚9W5õŽÅ~ŒkˆºÑ:Œ¢ª»Jx£§6î é Àڹǩ‡œb@#ªÔ@Ú@T•G‰g^ÆÂêõzV¿]o¶xïP¸!ðeÔSC]§Ýžæ6·»ÍË0>FÐhN2‚ïœjªBT$8挳œÁà-1OJRrü/Åš r ™›zÝ ¹W0A™}ãH}í^î ÔÚI6 ãHw =9A¿IŠNdIÆm2W£<ò9ˆ€;np¸¦Ò(BŠÄëIMÀ]"b ú®&o³ÓëõúŸÓ§óét¼*ùu:–ÑT¬žå æRQŽÒ3A‘837jÑÑ‹‰È ä¸V‡=ÈiŽ«ÚÜêÃA3)6 & 3DÉP=<Š цöMQÂ…=ÝFyÖÏ ›Åf³YËïÌö‹f#nÿçcpyõ_hOùÁå÷¸›\€. ÀÃÀ‚¸à×ëd¼à×É0‚΋ß6¹‘UªU`Xp„ç8ž·–•E,&zf(Öq‘+:0 \ÅĨs8jT•AØ$!ªVCTãrí·é—I± ™LP,TWÙª! Ë«ª«ª«m…à± ,óƒWm¶â¢êÊüÂ<°(È/,ü§BðöïÛÚÛm¶ü¢r[ûáJ[~~QþûÅE…Eűµ‚¢Â‚ü¢¼‚Â"°Ix€ŸWœ_i³µÛŠ‹‹óaÖ8ÇvKéM©¯”úÅ)ÖwÔTÈ$ø¦ ?ë&ÑáÀ¾qh¶ªU U$/°®#¹&”KÝ óADÕa[u»­ ®ÚƯᥭ½ª*¿ ¿³Fa^aaa~Õaøîa°ø×Þ^YH£¸¸ÿC@+h¼U€EEy; òƒä£ã£½Ëóóó`yC«¡dgY©Qõ•PP¬)ŽÇó7j¾ý3`…«µ»ÝÑE¬R=µÿÇU•CÈkØeî8ZƒJèjã]e©›ƒbÃÉÙFXÑGU•ºlGËê*[UUaA!f Hù`Ø·UÛþ„ß‚¤è}@ÿ¤Ð€CŠ'ü©²²<”vä0„íü¡••¡Ñ˜ z£µ´ÿM}"-TE‘†*wè²sÄÜÝu„Az®Æäh}ýѺ+=‹Èh ÅÆBDU˜0Ú𲢪 í¶êüŒýû*öï[ö”ïÇÛÀõdTØ/ýaô$P¤:E,ó¢ _¿ è“©A¼~ o<{$Å2\Ÿ——ÏYKÌæü'RÚ X[âh·U†úŠò½{+bÿþ}*)X–8öîW·Ù»˜x*>©¦ÄA±^ø°”Ä!â¼„îž ¾~‰¥H‰’©? ŠGã“6žMNLÌ($Ýca-€Ôx<FÌøD ‡ñnQÜÇC£2êŠèåqøñ£ðD8 ÅåÕGGe ?`ŒV¡Åj4ýgPëSµzh½е$Žv 8öåPcŽe•F ñ-*óÆþ=”8(Ö^<<¾‚’ã’ìnECó<î³!„8 ñõÂŒÆsÌ+@ Œ#‘0yІ¬ìæåUZÆû’XFèL¤)ÖÒTuqÏž€ö£_U<ì}9qÄd±¡ ì¼çJkб‹?F(/?hk¿tyðÖØ«î:˟ˢ܇Ÿ†æñÝs8' çý,EØ1ãáØXÿxÿø÷'±[BÏŸŒ íÇwL‹È¸4‚^Aè41ëZ)3Øê_8> äæ¡ Ü¶Çøx$ŽRcå Š!0“‚F§ó„ˆ½ªâ•yãEC8Üž Jkinºüñ‹(ï|þŠ»/$ <³S¬œ`$ã8x6…\—äà…ÎËÙQxz7ܘ‚1WÕ—yj°½2Mp,,KS—.VU_ª¼Ð|ïïß‘ñ×yäÃÂGNÞÀÈ¡_ÂPÜ¿Žv©[íÈ©x8>’““÷»Bs­jH±FÄñâË‘ÐìT(š{1ûäièîüôð4w…à~wBsOŸ‚c(¯øéoí/‰ò©—Ù©~ ^çû¨|­ðd^¾HqŒ€ç_çÃçÏóÐø¿#¯¢r\ò““ƒ(ßïË¡÷+‹‹vì(„L£Üj/®l¯jooÃÿ…1@à‡ÐŽýð`cEÅïWæìè¿Sÿ.Þøm“3VÁžŸ,˜LTqP¤ qÀrÅÔB³!øü™ã颜§³àÒXÎÎÎÝ E(qP¼ÔÔÔËuÃØÇË`ïÜKölnE†¨>SzúªÍf(1r‚€±þËBø|ð0Úóòó*ÆàTIºù ¿„y~Å9‡E V·´ÃbVP?rüôcÊèê¥2ÿýâßGús *¿$ÄQˆ69Ô߉#ÇV%ï6Òr>)C@%Þ¸{7D¸`öéó©ÐÐÓW"޹¹§„o }h Qâ XeóÓXçÅJ4ü·¿L8Ø–#Ž_õ Љ¯Òt^¾;† ééá‚"_j‡ì¶Ê¢8 +/ã-/‘ú"0ã¯`Ç…ñþ¿æý²j?\YüOhã Ðq1B$GåHpà¢íOí•ïƒMÁÑó‹òÛÛ ± Žè ˪*z P¤qD°… ˆŒ»¡ÙDF˜M"ˆØ³Ù“ž/¦—Y(Bàñ(qP¬žßº”D?»õÜÇËã%re©P%Q½~«°_{\Vk®>˜¿£ðÐ… ¨8òÛ« ][ÁR¶•ÿþõ×_æ Œôç­³Äo|ýïïc&ÀGÙA$GþŽêÕÿôõx? ¤à!xÜ|âá€%Û­ ÇTQEêGè¶J!ÕZµf_XY–8ž#*ŠPâ XÒ¼TùÂø?¶Râ|¥ÏDŽgxuø‡º~Û+«°×º_žªn¯þ¤0?ÿýrr 7Œ=3w¸ ÕùòAøÑ¨Ü‡æ¦6ÛpøñãÇ_ï€dQ@\q¼6È/*úz4<>Þ?³Ìû$ï¯ðC¾Ïu«ÄQÜN‰ƒbí‰C ¹sëȧ{ö|úý½{wî„fgŸÎþÅJòô§/ÎØ·óÁ_Ø·ÿüÅ¿üH˜ãG‡õ¦¼Ó³ûÊ÷ìÙsûÞ=LÈä[}™Å 1wii7÷Ï)‡©7&¿›,øî)(ª‚®qðÛ?»qäå_À£z^ÔƒŠ<ôä‰ Ôíiä{¤+Þ´ñ8üø}›­ª½êëðèãÇàçýœðÞ/ÁoÑ—áÑщ¡Ñ¨\QQPp!ŒN‡€Žh·A·‹r©8¿¨8ÿˉ0ª;¢ abøë4’£ƒ€8*‹ÿ~ŒªWI_µç ÜðÄè´¢(ò‚ÃyYñp|ü5i*Å­~åK´WáxÖ/ƹ‡À* Á’.UÙ° >ƒ©ñ Øg®º:¾7Í«”‰£šê Š5'â}¨2!"Ìñ=$Žàž=å{¡­êÇÙ'³³àá "=`Èñu`7ñ|t|QA¬S0ðî-t¨[wîFðgØ.+”8(V†Îû*Æ–Ö*+üCÒqì#$ *oGAÁû㸪TŽ­ý[û|t"|w÷ƒÄÂv;‘o¼òá,™þ¿ã¤ï‘7CV.WW¡÷ÿy, é½ *ó‹sr._@µFÆQA’8q(´˜:ÅÇX{»­ÚÖ~+·/íicè{XaýÀ¾/ÔøÚŸ¹â $÷üyø|ÿÞ½åGBw sܽ{;NȦ&kQâ xm´/O/Ë|>vé`eùÞ¤}*W¤7¤qäÈPr íÈ)È/(œ˜˜ B±µ·W}j\@=ü¾œbWˆÜiùßAh«zü5&Ž~ìÿè¬*..*Ç’cÒCp<üe~Øi”S¨ J™ãôî¡XsâPÁÅ^Ýþ•‚+SAÉQ±çH(45×µ6ã8ËÏ8 ®Š…RÅžužW+¬ñdöù¬]r÷îC„ 52%Šâò²¼qùUvÇ”žÏM ^¾<8¶¢D©´TÊÌDÄQæÿÁ±B0üççç‡gäà¬WX0˧ÿ;ôpÝx†ÂvG€bBqS…°B.Ð(pÇ!\V}v!/*ü%’$ó²<il$|!?ŸÄZå ò(Ê5ÐpE¯Š5W—`aû ŒÇØÊio¬3õjÝD"§žÌþ¸Ÿ82–"ŽUâøâéÓY\¥jjþöŽß }uX Å¥ÄA±",ëãøêµóf™s¥F) +ŽjYQ ’Ø‘G~Á ‡rq´·Á@©÷a0nÑÐx?ò­jT”d` ÆßâÌñ_Œˆp↼èï`·Ç"Ž‘ñàØ£¡qÂö1U}b£×ÅšG5"Žç2,+´ì¿q¾ýØ5þ“šŽýTIîP+ v’Ž?Æ2ÊgŸtîýW ^îݽ…}(2%Š•b™„ŒòÁwê&Åô´ 8”ÛqŒCß8LÚ³µ_“ú¿/DV¤ö¯Q»¦"Uõœ)”åùêj¸mþ÷ÁÇþ½¸Š”ªRäè °a> ˜1”?¾è'ègï NLb2Éë'¯­UE±æÄ1§öǸš}úäéyÄÐÿýV±"Hbøâé“%|1Åã™'à`?ó LMÿJ¦ÄA±r,uðòà»þ+¶¦#ÅQ]­LNLŒ }‡Æõ*x}ã‚Så‡PfÆ0¤¢÷GbÄ!_®&òFóÂu˜Û£ ó‹†H•Üü)™ì39sèw9;0턉cG5UkNc8ÃÖÆ‚cêÒpøÿK’-ŠH ’þä)aŠÁ$ý‘´ý“§OìÝ[¾çÖ÷¨ à%Š7A’—ãà¥Á©5ø¢iééˆ8*ï? O¶¶Ë_¡¦€ÈGQ84úÇ×Ppäwb ÃþÌ|Œ8P?œCPX„‡Š‹€àÈ¿11‚säå]VTâ¸Û€Kîe2yOŒ+Ž*z÷P¬9q VãJU2rh}ãÌ>}:‹ ‰tÄĉĠ†ãþ¸ˆ7b’c6tBNð> K¦ÄAñ&PICÛå±5Kc¼‘ÿ‰Q 8úÑÈÞ>6…5DNÎ/+ÞŽ~ù>àüÁ~Ü1ìÏ}…‰£•e¿€ºÁ!~É¿þ€äؑӞ_4¬ÇäTrr|‹2W4ªŠ"ˆã²jª‚Îq\&¤¢vòCÄñÇÎdS ާÐXõ#rœŸïH| j?Iã!“û=Î ù$Á3N‰ƒb1æ/Û.½¼)“rkpp…Q«g©ÊŠã—…ù££á”œ—³cL™º„¢mó.<j# Ýßö~²ƒÄÒ„CksW…¥¿¿² ?ÿ_ò+1S<3:>2’ðfp § ŽŒÂ`]ò"˜Óþ%î;N>ÐÍ%ŠT Žç˜7lÇB÷n}š7ŽÅÇžxc’ÿwþ§'?’z¹û:Ÿ&Ä}Úy~oEÅž#÷î¾wïÆ‡D‰ƒb) ÆlüKgªþ‘7Kñr EUÁžãC¤TúJ RôÄßBÿÅ賑‹ÄßƒïŒÆ7ƒ„ñð!–£áñGÏð‹#ò`ÞaÀP° Šª ‡á^¨:.ª¢Xkâk'Gª?ýo¤PÕ4ìcµppöÇÙ'O^pd`t<…}ÉÿòGh£Ú ó;Ο߿oÅ­;*H›mL^U☺ÕùÕåÎÁ±±©9,âic›u‰çƒÉå@n¥èßi6á%` ìã@Ä1ÒO P͇aÚ^ Œ>YªªXŒ‰ðDøÑ³Éñ‘¡ ‡]®þPyÈû>}"L¢ªhëXŠ5'ŽAD—/VWTKãâF€ `õ_÷v t¾E!·*o`!²ÿIïlǾýû÷í?|ç®J ˜8.+«IRr©ì½«/ NQêX´qùµêÜ®!”Ýx™ GzZœ8PPUÁ!dI .¢‰¡Ñð2¼12±ô[€7MLN¨ªC;vÀè«ÃyßLjCVT ÇMYH/y9ñ})ôÑÙ¡‡óŠ23ùÝ)\~L‘PËbQμ}ä·dùµ­Q–0t£fíƒSÕ‡ÇKªß½{çÞ‘Š½{ÏÇò40Gà¾È`už´q•Öàür5F7DZÐÙòÉ”¼ŠÄ1·dѺòNzÑ®/ÚXªla*òüO’ àÀŠc.†‹8äû~5â‰83F† «c>B ± ŽÁnâЕ½q–¦-@÷'ïÏ(Á±±Þc§ª«ó.À‚‡($‘8¨¡7UñŸ%*3ˆ ²&Uðƒ}ð1jý\”ÅëV³¥Ìb¶X-ef³õó¾0ôajQú ÑZË™Çèx÷Ë‚xwS(Uˆã+⟺tø_!m|ç. ©š›û"9Ÿç‚“¦Nûœïø1Q…|u q“1‡$ÇÝÐûˆ8:åÕ$Žê7©YD‘"´qiU{+½EM3ðVnö‰€52PT•ª8p|íÀø„,/Œà@)Bý#AâÇëãÇðúPp(82ôp|"<ò0gPõ‰  6&'ïÏËCÁ`᎜U°ñÙiç7ÏÙ±ÃFGjB”þ€×,f+ZúÛð¼ã¬:² FD‹Q–¬–’‹•ÕëYVÃñt/Ä;ºÎÝjh€~í9è¹x ~ž,…Ÿ:ÿÒù—¿ü´øeÈq£Ö“9âGÞöƒÏW“8ÆÞ¬ÚE*`™Rç©H¦ šü™L¿Í€¾qâã€é{Ø7ž‡" †FúÇPOŽþ  AÀ£@vôc™²#84‘¶AÌ$ýp—ñññgл1yÿþ¤<‘'‚cy;*rª/ ÎËóÏ"‹ÔOU -Ia,àÅõߢ~aüy="IÒõ’Ïô‚ÀXÕ°l6§Ñdei²´YY§e9ÎÂéõ=WbÑïúprFĆ-åå–°wOSjÞ8@…CÍ=yf|ñµé$šyú$„ˆûÆåÕ$ÛòõµŸÓëv}`µ›+½UÌã¶/.?ÔïÊ(ÈÉÙQ~8¡(I·rÕ¨%‹Àóp8 ~'&ÀzøYX’0D%<HŠøhbâQÉ…?$Ä|(É¡Õ6J) x9( rMÀÓ&I±×%«ÙÊrÐhXÁ œx/§Ó ‚ õ ÚmZ-÷á6À—ËqÙ¹†2«>×b,+9Ib )eˆc0¡àyH„{/ç'³K¿:;› 9fo5|óƧe5‰£rÅ}(RÅNµÜ ,OEê'7ë\ˆz=ËäL<š“&ƒ¢ Xnä1úøâAø1⑉0øä1­²âE£ÉüÄÌÄ£™g€7îG&M+¥äY,U)‹Sa´ˆÆuË$:Ùº©ïzI‰•e9ÞÀòÀ\«ÓÉë4:Óéñ5ùvi³´Z-jX-% ä«ÅlÑ+ŸŸÆ æÇÛ/MZK/Çbâ #nèÎÝÐÜ0÷³ê"Ρɩø~±æ·äUUË÷žþøeU/_¾Ô96G/ñµÅåuÆüÒxP†ŽY,µr ‹Ùý Ë“X6(!8B$Ɍǀ0‡!k< 9hÐÌL3MäÞ$š–NÀø[1ž|†j#2‰ÇéY³Æ}ÂK“«w÷P¼5D†œÓp€—<ÍØ>eÉ‘ú¯ŸÕ[-%<|‹×𬰅 0‚F€üøU–†ý…ÆíÑétZð£å´Ú,.‹ã²²yX³UÏžó‘«0*Jkm¬Š_|—ˆoœÇYD³sOŸbò*bjêe „ÐÅ3€K¸ãi‡Ýû}èÇÅ—HÔ]Ë«/SöXC,wþ¦hLµd4¢V}òn£žu:¡â€mm•¿«¬®¶UWîÉ«²µãBêŰj!¢–¢â‚üü¢|ð[TXiƒ:¡0ß·¯®(¬OmU…ùÅ•…ù…yù……0*þ8Xü©"Ý"‡+*QnnUeUõ"Ðk(%§â0vg‹á]»8=†“Û5<ϱœv—ï×Ü6ÿYT|àç½^ƒàõò­‚6S«Ñhµ>§N `7Ç,z o˜¢Wª#e|RÜ79óN’Ƭ“‹B«fçæBªy¹ÏcØ|–TúÅþ{QDUHYUâP~¦ùô¥ŸÝsÑŽå×¶ì%Žx!Uÿ`i/rwZ—³ ¯IµURy,Š’‘O~Š‹+m¶vh`²©ÃþáÃUU‡m«b).¬¨¨nÇoÚ^ ˆ¥A¯¡Ô´i*ª‰QQÄ“¬•cÍö“-<ϰ¼U¿-[h=9ôy¥j@3åà˜±qƒ1À‚NË8­ YZAà à½y×g@pwÃ#‹ ñUkLS¶¸oàÐ sñ¼oü8;;»œ_c]‘è8šwtü4’WWqÌ­ÐDZd4–mrÇ»Ÿ—-}öR-GZô$"JfƒÞåÒÁZèEUUípÀoo‡+U•ˆ%Š È€` ‹ àzq¡Í†¹¢ºMlhk·UV¿_˜_™Ÿ_y£ú°ÊŸàE%ŽõAá(¾<üðÓ¿)àÉÂÐ !ÛÌ‘ßÀúyÞ`4š ,ÇŸ’Æs'Q˜­" ¡Ë«ÌBE޶xµ:­†4:§V0ðZ òtx½y7'Êe¥7ÑÇE=)B‹Ít¡Ù¹»w’1÷äÇW¢ h²šÂ‰¡¸Wd.YeâXÞBþó®Õ½ë(gó)[Ê©?ñ#Ìb?šKJóŸË×Í%,ÓÌâ(.*È/n‡* ý€69´¾(ÎtA U6Š3T⡾¢µO®ªLRYQ˜½_X‘Wñ |g~ÿ“¼ ´yq1%Žõ€V¯N*¤¨ 颿iRŠ~þ`FoÎÖ³Ëó¼àdõ%zÿøY«,Ïä~‡v ïâÐ2׈ A^ˆÌ k8 «ÑhAYCÃkÀ8ÖR²sRŽü åFªäq,š †f‘̸Riã^hvù@ª%üã8c\Ýiv.ô¢¥êÝ{+SL™#Eðõ5•r£Ô`Â…>ú "²˜Ì¢™ey £ÜP\Tdö¥¼J¤%l‡‹ C£èýÊb@ ù¶ƒ…6¨Úóó±â¨¨8èÅV] X#lPP¨#¯âw¿«¬BqR•6¤K* B±•ⰵǭ[”8RÓd$Nã§ÓâPx—^ÏÁ*ýöl¿ï$#œ*ª‰Iy1fN]‘¢C: N§Ëâ`žǃ Žÿ¬$(½(S‹8ä±6Ý‹™«ˆ¯ЇÙ奉³zJv¿{/47Hèî½Èk|ú«bìbùk‡ä,Ÿ7XNýäï‹Âq/§dþxßìÊ$4-HRCÄ S}ç¯4 ãr;½d,ÔØzIäFæŒ<Ü[Eø†2V³=ÐfÓV«^žx3erì0øÓÔy–¤¬µ=›â-š°¶”ŮȤÕÀ8ƒßÀ~¿Ïç±k;ñÊÂÖ­h4v‰„K q:žg4ZN 3>X”+hØI¢êRàzXNÛ>Ù³{ImªýûìÙ·ÊìWW+àpü臯ޜiìÖ`祯Œ1ÈÁ©•Éšq¾LyÚˆßåêDQ™1²`È0&‹qº]N,CBQ¹Ð穈C¢H ÕÅ'‰Rl€ Ã¥2— h£AADq?šô‘ $c˜2GŠ!ŒNKDþ|ŸŸsä,Ÿ‹¢“;#œû;”¬_´ÑtÚÓ¤ãÿœøS?à«àñ)tF·N#¤Î Ÿ4áÚËmàpçœ Ã8w0¼Às,¦*¼ÁŠ?&êN]â¨^4ðW¨„°·"¾þ"öTæ@ä±Ñ¾u+­47666õÒÑgySÕÇé-ñ®¡–)LiÚvâåøx0ZÁüg~Ápÿð†ÛÕ,‰œñ5ìŸFÓÎÞçC˜8þÓˆ¢±Dg+)+3­^?Ãó:/¯7ÍæÑ‚6 ™J“>uÞÔ§@ÉrÃ=M¯”TB )’|ºŸ'Ïçˆñ‡N{k(Ò‚ÀXVXƒÏçt:užó±Õg âP)._Õホ&g?D¹A²…œ~S’#`G†‡®§é žåyò©áSC)KñÚê°ÚØ_ ÖË—'޽{*Ê÷cÕq ùŸ¤Š{OYž8>–è=ñέUƒÕå/¥z0ãä=ÇsÞçãt‚Að,F“S†%¸ät–"Å.?^ÙiEç(ìR_—>·¦ÏÂÁT?ŽçLVÎËû·{E9âÃnéÔw":»Ÿ¤@zTðú"òcŸ¿™ñù}^_›—o/ȧ®£Í#æÈ‡8ÃÀñ,_Æ¢v|W'ê!Q€@oÏëz]\uÔ ¶jdU% ‡½«ìSK˜ÃÞ˜bÖ‘[6ŽKñšð77¡eÃv–5›5(² üzöðxY² @ƒ’hFNQ9ÇÈâ©aý6X®Nàg´Z¡Åàóù‚¹Ô´Z0O””КëCpF™]T£§å(,& ‡½Œ…ÕoÏfAo¶¶{ÿ.#† ÒsAŽ¢HlÑ}ÚMàijs7·yFçÓè4š†÷ýßh¦A)¿àd`]FâòVó¤´^&‘ãxܯ ¹AäG‡*8ŽmÀÄÑSèäZ"q€Íj»kuøÉ‰Ôû¿ÍuV—¿f 4h;xñÒ nN &lð×G ` V£$Y?ãr¹Ûn„Q[hhÂV¢ã¢8ý‡“œADZ·‹3œÔí2ìÒ´ú|·×`6ïn¨ÆXPŸ#òBD}UÄ^ލ,ßȆ•A8–?é…AØx¥Hjæ`™º‚ša¸Ðxp¹¼Í^ÆçñAö౨N Fý>\cp~¢g½ÅQÖMTö;”ö«k5`ø´FåŽzG]OI{ŒOˆ© ‹Žk€FàާªkïùX§-Æå·^2ó‹åTR'úæ´V¡ /‰…¾qd~†ñ’YسGcCóé&ïIk©ù÷’ÚÙ‡ú7R3dÄRSónŒ¢ó$6Ì`A`4¢«!zºhÎfàbгY€7vfûE3޾–} ’⯴§ÎXyð`çË IÍêÚèíµ Ùà GŒìlWc%5¼W£aËÓtÚãr;]MM.ŸNÇi »ƒÀ lk‹Aï/Ù  Æg5M#à(JK‘Ý¢ÁT†ö·Ðï6•N³É„'÷~RJÐß‚ÓÂKhö qÊžèvÉ7¶³Ù1²YŽa·û·óŸ‰²Ù„ƒf}΀&éŸÂn¸@\nÌN'ïìÁð0¶Û /"§à‚…>ä@—Z¨¹,Ëåò(+í“o˜ÖÏ4£Ñîh¸Úàˆñv‚nÄ%HfjÈõ€V«Ú8qÔ;êk½Øo¯Eú3X\©¤ü"­ÅþrIw¹spPÞ8ß“T '}}°{›ÖŸÓúÈâïtºV­ð+NÄ+¯ÿÜÃûñ‡ÏJLF³¹Ì¨7`à"CTýn†°ä¦©á)…ù`¢ÐŒÄªÁ(D‰ˆ8ŸS’æÁ¨ŸÅqœže·gë­f¯kâFðiU¢â°Kçƒ9¢Ø8 ߸<@‚zœ>ÀŒP†V£Ñ±¼NhEx¨-$"Žmk5XwZÀŒsÕºaŽú#uq*¨ï`âè²C‚@.ñöÃÈ«žÀ5{}j¬Þx%|õ¢3}o –äK%Œá„Ê[èÿ„nܳ¹Pm°ÛYœÓ c_ÀƒŽ1@´ ^oÛçn ëÏZLFS©Ù(£¬ì.Ô ™3Ò¼Œ”ŸIJrRµÒ¶7!‡#*/À5Àés³9V¯×[ |«(Å7‰Lª8}ÚE¬S~—ŽaÜmži1:ýàÜp3ãbã¢ãÿô9Åë´Œð7äW¤s7”ÀqNo6ŒÖÒRü©}âz¡ŽF»[OØÕ[‹iá(xþçžoëbœèv$‡c£ð†´t¯b-‡…K¶˜,ƒCËr,´RhxÈoðøIÁ¯7è9«Å\f*+ûí©aIJÊK~(R÷«+¥ è©8Izx•Ö Ë<§ÏÎÎÊÎÞ¾]oS‰“0å[úœøE¢7œÍHe49!o4»Ïµ¶¶FŸ»@æ<ýØãñù<.g-§Ói4^ñ,* KMî°ÏÉk”±zOK-&RS ¬nÝ\; 4€MSö˜å òH7^¿‚r=âÄ7Ú ¼±|…«Az--7nà‚õRÐb*áÊJÿw–”.ÕÆ’ÜÜÜì\ 0L¦\KÙd:ÁÅÄÙ«ZÖl' |¹íƒè…“*è÷©Žp\Iê#‡-ýÐæCØ–ÍÊfy¶ÄÌÎâSì9} QŽ:qÐíiðã ÇO»ÿ^^ï—dH(MM.'п4÷Êóf *‘+µø4ÈénÙn-Í• k­¯9G£=ÁR…©MWj  •䜿 Å?Ónð½Ç–À`²Chƒ¹ƒ$Y4èÍf‹Ù¬(ɵê³áZ©Éx㦸óEYéçh‡ðºåûŒ»—9â³Ì4„­#ôÚYÂn´ç˜Ø„fÄ¥„P,êÍxcg©¦/æõ§ù f·ËÝän GbÖ¥xí\BYv7æ€ÁV:F£Ó¨@?þïŠC¼W&%zëîR³zðõæ »m'Ôa‡<ÑkO– Ø7Žü@o"k؇6ÎÕ»|jwxÑLup£÷Èù¨hÊüÀÈFóÝÿ÷¾ÑBƒƒÑ„¦«C>Џº$ò•;@sp 8¢¸Ô%8–2àÉ¥,g)1›g•·Z?'¦'HµõqæpÔô¨ùáŽ:¨8bQU`û‰ä´-Ëô_l¦Ú»± z"6>E$ñæî†Ý‘Rc©ÑGi_¬véMIÝÙ5Dìò\–hÓâØJ'"ˆ3n7?V¬öºz;˜ºÚŸh¼ý¶F”áä9ýÂ<> gÉç5à(+ñF˜ôGÃ,§g ,ÇY³ÙÏňu« éL8uÚÕä<í k¥¿W.‚ѽ¢"·žAOÃn ëvº=M:X¼Æw›±ÜnYp2§vrz³Õj,ƒz¬~Z=뮺~¤Q,Su-1ÈÊQÛK’0qr@r{C]ÈË·ñ ®áòFo‘¥à²C± Mð0©(ŠˆiCC§”x¡iÙš Ä‘ñ[jœ 5ƒdg¨u±¤bÈ¡Õ7]IŠ»ÌylI”ŒF|¦ÌIJh*Ãæ%=}¨Ñ°ÙY0¨NoÌä"º‡Ñö»àokq:¡ÊÝÜvfû9ü·¦eàr¡HÔãD“ í–´Œ´ÿ-Ãétr™[2Ò22ÒÞKOƒ&Kð$Í ß¾½¬Ìj4Þ—Ä2Âj¢´þ&PtصÀÕ$ƱkîØ“+€œa¾‘ÌTøb^¶s`;¥Š¤ÙsõF!˜/kÀ÷„ uá¸p`Á~Oé1iïsÓ4Ç‹mÓðÍ…›ÆÿXöˆ$GZæ&¿€îBÖHcê®Ú=§u'W]w(áš ôâ"Ÿ0ƒNHzzçøKò)Q†C|Zú?rÙ,§1šýp(ŸŸ!‘ˆhlsµù§•È0à 4Àp4¨¢œÃm¿Ò°}ò´GÃoAofdà­Ð"]Ÿm±š ë]€*p Ðè=–ÄÝ EF€ è¨gýØí w=+ƒË‡’E·4µwã7:)C– å÷¥ó‰<"·`G¸(à¶Ó2ÞJñ˜PM Ë03‘8Òú7óõsû8¬V¡eÂdž@]’ç´»¦þÄêWÛ–WÕF(Ñ÷à`žžž­HXYF €¡==å¹l#û9j¿€]’ü8==-=#=½° ¦ p ðí·µ½?ï=ÄéN­NƒÉîŒv0æ› 5Ú«ê®}SgO ·íJ.2r¥7è=r¬qC&8%d&ЀÜײèm,n•H–—$&4—#n’6W-ZÁåçŽ[“ˆãÃÍL5ݽW¯Ô8ŽÆ•®oj“RÃj`\çê4”Hõ ûXT„|žNÂG4ا¥K²Ø„ËÂH˜2Ù,£™%±¡túfó7·o (æÜ94fb ‘ž†™ÓÜÞÙÜö jŒô4FÃ1DgÀ÷ã›oÛ=.šLÁåiô˜Ñ ÌF8CÇcà,vuøšýøíÍP„çù`{|T¼Hy#Ž%Ó$nIJ,jô'6SBi™ª:ÐÏÃ&Ô!t¡Ô$½À9KàY2qlÛŒ×Îíc‰•Tcý°wCbêŒäìAU·ëíÇVÅ^µT¨Rü%8䃡½&)„ ì‰øÐàKÜ<êq·ù\Mî6de‚>pI:Ó|QÈ…ÍQé[o¸ÝÄS|>Ø’AÈ%#æ"OOÞh‘Ù8ÆsE- ½µ×;Þx{ó”nS÷nÌ2Lo†[K¹€66³J/yýÕîýmÉÌñlÓ]9ʉ„ì0ØœZ ìG…T“Ìá5jAn€Ul-æERjø<”ýç°”‰Ez9bÕóD dðÐõ‡ÇÈ4)EݧNW“ÓÉļ’¹u7Ãj#š-$¦jKºY>t7»Ýž´Ì´¬_HýÆû[±ØHaçÎL´ ØÒ½!…eèöíÆÆ·C¡M7ñž¼L+«'ãÅåʱMý…¼j§¦ï’‰cÓ•¬ºs,Áâj©µGc,RãHÌø¦‡tˆ[õ$c?ª=N¼Ôñ’êpçÒRÆj²2±I) fdD ’™\n§“ѹ§et•‘Öêrv‰…µÊ¢¸_ìi·7c+òsd¤9ÛÜn'ãtÞQÌ¡­HȤg°üö¹¥Û¿ì¢# ŦÀ ËiäÀ+a!#m3§r„ŽÙœû5È5˜8âåð0PM¼x„Ϊ¶U¾[¬¥™4H+‹ýF §Ñ¤a÷ Rz†ayšÜM£A;`'Å"ŠÓ§Qލ¾:P gd9û9~í¬ájU$›‡¼é[Ò †l£Q”Iê-£L±ITT^!’ãª6Y*Çíúä6>öÞq8ê®Án ñ÷º`G Äû[Í4EbšR¤Ý»5š,GËf Â¨ËSØ€atÌcè-‰|€]>¤8šNÃJUCmnR)wgfd¼·å×Nï™i•…ª˜e¾‡˜#·–#’ŒÈ)9V”õjÔønÛÖ­ÛþJïì•c3kÊ“£©¨%ïUÑŸ¶yS9ó!$3 +¼§§·Nõ”wãFrµ y«È‰ü2ÒW#I„ôñ]%^àoðŠüÝYü¶Ïårºœ>gú/2ÐÈ”¬¶¸Û âhÍ‚DÒ·øÁ“²âÙº¶þ…tŽ ²å=¤92KMzµ†?üA´ ¯Ïë9{mÒûé½½¢+ñÒ^0€Voš™w¢äh§fª×@æ¦Må¸mo¨w$Q" Ü¢ÁQóM*¤ 68Z_Óƒ;^Ið•×5Ôß}Ö{ð<¼—.oIGizé¹Y¬V§ËJGƒ}šd6ØÚ>·ËÕätº›2°ã;ê#yÍ7Üg“ÇiRÎJþ‰™Ú%J8K< ¼.`ÓTzšqwÛ»ÒÕLÁõyN'b¦×Œ zo¯@l¨3ð½ƒÊæ˜}R3ÕʰiS9nrèN**‚bûéMSPn`½Ñ²rÔv×Ö»óþ.èjÊ@™!µBþ1K“¥ÓhñL:#*ÁºCì Ûänr9II*™„aeè°w| vsûQ–¡4ãÎÂá¹°ö!"9*4e¢Ò&™éeÄØ9Žˆc]2ÇBB)„Ìzs¿Éüûàæ°X)c¶![^¤íØ_›5•ã.lAÝÑSSïHÇí \;¦öŽÃê&ö`CUl[{Wà+Ç*{ÈÉ<¦U¤§eJ¢Z§P#ht£ÅɤݟÛKá:®3é±±š;¢æî dŽú‡!RÒêŒÛùô_dÀLìcßR£ U”qª`Æä†˜}Goî×…mSF¦P+Õkcs¦rD`<LùëNÈÕ¸‚&k8ê®õ`õáp\‰ñFŒaŽ\źÄ~bUõ¼TZ*þR騬a’Z­Ž5|®àª!€”¨Ï tr¸¬Dp€aÛö3N£¸*dØÊHCUL¢`÷ Â@ÖIcåcxžä—âÐ’r‡¦õyR“c?¤÷ö몧^œ£_ Å2Ø”©$ïõò‰‹Ž^LWÄ7½ãGk0o`§¹y7º¾QÌí«úg•RýC–D‘ë³´~ÍQÆÑ¹ó€7tn·vbªÃ¿´EEBú°^ÀýÆ·`ÃÔø¯N·AŽq"†È€[nÁA¸¼åqm*œg(K\.Nrz8~›FÛ¼ –èàq™ÆQ,3MKÛ„·[#qŠ;`/Ÿnb‚²âè©s8Tº¨%lÒèUÇÕö&¤ÛW·Dž(¡1=-M’"°’¾g°ìÊ̘”£­Xn¸ÝMn×<¢™t<ðãõ ÀmÓ[°‰À\Ž&ÆétúÒbÚ„TËeXawvZÉÜ@"‘¡*##²>kªKÉ[é½ýš¸¼TÁ¿AJKb¦r„â±Q0]ã›ܲ©›pD‡ûÆÁêoPG éT»c=žŽ­®›c+Ö²4Ÿ‹Üpˆ× q;®?:¥Cv*Ø\ÜåôbCSÚYAÙ|[ÒU³®’~ °‹»Éékn}ˆ6ÍLãä(.О.X¦L\f$ 6–‘÷#?ÛÚ™šªVƒ8>þøuS,…Í—Ê¡·'tÙàrêj$ö‚«:££+Qp8ŽÖôBÞHt“ŸXUÅA*‡È}Fƥš¶é¤¬:zk@ƒ|â8ÜNçc²$ÿ]!,‚¨HAuجÉ#è4BZ&Êç´Qq ®OUj2ö!gø‰˜M“ˆnÐ ë“7”dÁ‘vÞÚ¯‰eZ–Sæ x…nã‡16&do8:PJx ë˜Ã~¤‡¨‹n(DÐÚÕ«1¢Ê ¨CJ‘ØW5&7þïIf_“N‡ Iaâ±})( '¢ÿËéÑ<Æž 2ÄVŸ(áh©ß@« ætŒØÕù4éD¼dF…¦æ \5·Ô"GJÒq÷&9Ò7¯ÒMÆ:-«>|§ÍSËkB*_¦Ù,eŠ%°ÙR9"‰]¨ußþ© :êzT\íY„À‘£;’W’Ò¯âµ7á˰ò.Ɖ›l¤m! $²Ü®&Äî6—s˜ôo‚1Ô° zr”g #ý=¯~Í¥©dÓ2›N“v["È2F\*èrFZÌã±þ°(<ð7ôÆ~mt.C_ѯ†âEl¶TŽÆ¤qßqD5PuÕ«.ñè©;Zס®'YeÿøÜCúeKZ&n¤ÇxÄLØø”‘‰5øuž]2?ÒH‹ñŒ4QvcÂz"†^AÁ’±¥©¹ÙÕœ†÷G5ýHW5ùÄuyVY\Óhæø ŒK7)ÿø_hÑKŠ—ÏÕ6x*ÇØÒ§Þáp$§oæè®éNä üKŒWŽ#×ãrWWrô‘>Ž}ØV„ÆÿxO×ôETþÓCqL¸š[ɸÏ,qq¤ef´¹Ü™éjO?¼ÞRëÖiYCî1.M˜ÄÒ±â€~æ´Gã®OçøÉñô¶^EÍq‹~3/bs¥r4:uµ55]µöx¯?B ×z—Q=W¯Ôz•F¬«}cÉa6á%1­€¿óý«´C¤Ã êDa¤“Ü ²sšÓå"Š# [ªÒ…f Ï[Íïaâ‘%Sd+ÀEÄ!êt(Óc}:Ç' Žz[¯S—"Ž1úÅP¼ˆÅ]96´W1â°w]C¡T½GêãÍþ^u“Ĉ*’×qõøÿQ»ñ›§p¹IÒ(<]u:o„j¿JOwyÏE‘kô§s:“šˆå¢ÕeñÜî²™„ŠT[3±ÆA–rWƒs×>¤]W c•”8(Vt×mèTŽÆº«¬ WïhøjxÁPuôJ n5¼"Š…ÊÀqR*¤a†(¡Êµ¤™)î¶l!j#æë Ìáô¡B†*›d G† ÕpFcîn‘l?ñjæ¸DhžÕE~º4ZUýM¨ãàâð*Zʉb)l¢Tåø•ozbÌq¥î¨=ÑXõrÞ¸jw8 ƒ„Ї:ÔëÞ$—#X:ƒíàïÅ4’G‘‘ž—Ðk±l†(½C¼N¬&H@-YE¦§÷œ§¶ÀÞ¤ùª·«åXƒÅd4oÊØ‚ˆ£Ó”Z.` ·zo–Æ]H³Æß ƒÉÌq‰~#Kbótå¸]Ó]S[ÛpE­”^ EÇ+«ˆ¡êh]CMWWMm]ýQÀ0³£×þ&é㦠$¹ÔôÛ÷HëÖ˜Þ eB`@.ÎÚ#–'D¨Gú{`í=èÍØ’¾üün´E”cÆGX¢ý¶´85ƒÅh*ɽ'oKKÏÜúœ(Ø÷› § J›õXod~‘à ßt~•è%/§‚ƒâ•&l8•£±ÎÐpUµP@t^ÑPUw%€ÑÓ}¥££Š—ÇEä"––ä¾\èZúP¿+S Aãû.ŠšÄΫ¦,ªQÕätáRU.R!×ãr{|XJàýN¯¯etzAV”¡°ˆR3&çÃ<Dz¡Ä\V„%°ðÀ@˜­JÒ»~3iîßcî«o¬{‡26HŠß 6O*Ç1Rа¾®÷Ìî®ÚúºWS½=ߨf.ÂÐ[þFuGȰ¢¾„u2“a4ºéÄF| QX×ådp¹7b\^ÝÃx=¯}þæ3ç`É* M’çÃ3rx˜„b`ÍFãÍR2ž’æ´R”þ?ÖûIiµ‘·€±‹¸ÅÑz×c¨ÍHe'=£o›%•#” ¥¦jÀ«+½¯(9^XGÞò7+u(eÔžoÇ8­§N±4ªUN9H‰cõ±)R9`+{¢¹ÊQ_WÓ Å}•b‡ßÖ=úoÔׯ4äŠ"+óF–gYFó_t¿’Ãåô+ªGBóá`ÿpFÄkIï@&RE ‚ 3ó€5Œ¿;ÜO–ÒÙoc0­§N±4:iCª5PüÒR|"¹‡¬R{¤£·çÕ£«׺;`½G²ê°7®d2#?îÄà£ÏhNxÃÝìE ¯öL#IæýX[<ˆà>äF?$:[àãcQlñžg¼¼Þh4›çMfD !SiҧΛ¾ƒÔ#^wOo¼ -oHA0E[à¾l†TŽãv•2 Ö¨íê¸zíu-U¸"{wÇ‘ZÜ Ѿ¢x\¥Á8€ Ä“9ô€8 ƒC£a`ޏ«-×Ôß4F{×…ðùBKr‹( ¦ ²Z1‚<ÚÚÜÖ$øü0^ž7saB¡©›±Jï%i ň«c==¼N釴¼á#5 @ {óÆEzªÞæu娍ġêŒîo¯çß,¢kÝ]5ˆ=V++âMb3aÍ5°èã`´àG3Ø üÓê¸/ἮK¨È@xD”è´¿ÍínóëÎü'kXM&ãýyŠŠè"džú±ªé+²®ÚE‚c€ÞÈïs_µWW§f-«¯óÆ^Z«ñ-ÍÝ6¾æÄa¯=ÒÕ}-XqG"wüùÛÞ+] 02wåÕª`†xÈÕX–…i–aš¯«ù¤ %yJG(K %zîtS›ÛãöqŸôüïÙ2cé<<¢$oŽº‹ë©S÷ç;ÄsÒ.°|0õþ¶ÁżQN Uo ?•C©«íèU«®°åª£v%Äfþ3ÃHHfÏfA÷8«Ñ°<ïdœn§‘EÓnd–š¼‰$Ü2Ó÷/D9Îç?ã÷û}:ÁÇéƒÀï´æšÌ <®b ¾øQq|·mëÖmÝƒì¢æ•¹®sêA:w;§Øµ¤<ÁÁ1HOØÛú²7~*Çq{]mMG÷µ? ëÚ¾9kÀäÝ]GjëV^·¥Ù…– €3ÌÄ xð ŒÇëQdÅ:A¼bÂVªsŒ,ž²8NË >àkÑé„Ö]_‹W0›ÌAk扒’åG?9Ñ™ëÞ®£œMOºdAǃl)fº¼˜7hÖø;“ýpövÂ^ï¨w ö¸Úøæç„GàeBã›`ŽøoõoPX}^Ä‚@4ðYY%@mh4¼üúW“Û3…Ž ô¾¦OÔtzŽãvýÚpòß.ƒ¦ÕçkjöÌÆ²ë‰\±ü:l?Zçíâò†¿¥7ñ»ÃT ‡,I‹›ß¦çëíaã§rOÄ­­­¹Ò»³rŒ_%A¹$w¥UãÙ{"ÏjØ,(8XÐGëG­7<ÿµU{깯­õÔ)VÐ8VÈb­À¶ ZN«|>Ÿë´ËsÒZjþ}™¼ÄØa—³î$–vú`}\ ˜N˾C ¦°-hŒ:Æß%6|*lj„ä?Øj+½¯W÷Ú•Ž.ÀõI‰oTW2Ác6â Q‡ÆëÕhF×äijjs¹›šš\nŸNÇj »A`¾µEПÜeuº\Œa§ÑÛÏ*Á²29¤IªG%ËaËRŸ–ܼ"s]'ZÓzê)b©J5æ¸E38Öp·ñîÃFû µBG5×^GotwÕ-Jþƒ¿oÒ=@vð¬žgù,žÑñ>N§kÕê4:À ¯ÿ܆ [?+1-æ2£ÞTj4e˜N.šLøx7CØ>¼djø¢à¹ôuÜŸ{`Q,.ïí/øŸ/¦ŒåsŠ:8Þ%Ò7x*G£Ý¾˜:õ5½¯œ¾¡6›­}€BoöÅê#gsÚа<† cÑ'ixÛλ1¼ÓZf2šÊÌF 4ÊÊ&E%zûJy‹féë¹ 9-o˜J¦*X *U,BsI–ž¬·ŒžÊñB‘Ãúú†î×ÍÔÑq̱šÄ…G°ŒcõzD•;ôPgðË VNoÑ›Ë[XOÝ≱r…¯V{êâHÛ¶Ngê´žz*Íê±/!UlB‰r¨œ:8Þñ­¸á¦p!bYB‡Ã^ÛÕ½¢¨\ :ê’xãØjˆt)h1•peF#Ër\Ëfaõ†’\£ ×R6TÔZ·*wH‘×ùô_ Žõêè å ×ÊÁ%˜#UÒìæªÆè¹zë×ÂOåPÔZUu ÿ½«U­4…£#ÉÍqb5ˆC”%ƒÞl¶˜Íú=W¢·ê³Í@d”šŒ7•ݘ/v—~Žv‡‘Ìè3î~O¹þ"q¤e¬GGÇÄ¢ÿÄ8½ß-žï]‚9>N‘éý åw‰žÊAâq¼qþx PãX¥ ªDÍÁÿ§h6¢0!@7Z5šï=’Ùl !â0šfàÖC>lœ™76¼F‘ù­K0Çz „øp34Xwƪ”iz1…Q9åwžÊê:–cŒÀKk&¬×®B;Ž$ÖÀnîˆ$ÞÜݰ;Rj„ñRà·´o Y¹)Éd´_š´±äL=c)æØ¶°¾/UZO=U4GªDå*Sƒ4÷aƒ§r„–æo^Qfô&TG t¿AçØeLU˜Pµ)QËIXËy0$‰ø¾ñGE$uñµ›Àö/Ei[×™©ç7´¼áÚCºø"qÐØ×͈ žÊ¡«¯oôï!qåZà•r8ìµ5±(¬@W,Ÿãø*üióe ˜ÛpŽIÓM’„â?‹îÐǧ[ÑÛ7q¯òmÓ/ÄFã¼þGM,i­Z_ŽZÞ05ðbP. ~ÝŒXHÛØ©'ìõÝhrê½Ú‹KÛ¾ \mèz5oG æ¨£¾® ›¯»e¯Ž‹£ ´mÀí—fÊðº;£B[¥ÐV’äóáóejX}.l[Rt¬§ÒU¥müö1ë‹“´?®¤ßÉfÄOå¸]_FmÝn¯­íºÒÝÝÑUë¨}79Ò)=u¨w 6v½5uõöÚŽ†UÈâ@!¶^&Û&¼Ÿb¸’‚+ì•ÒÖ·£Cz/ù/¿NïܵÂT9íìM±ÑS9; ‰.Rh =:ŽºÞÀ+GáÚÑ.5B=W{WO¬Îß&~_ˆÌ' *‰ÄhEY¡JøkúṞn2:gŠôÎ]3æ?X¿ŽZO=µï›T>G¿Í‰žÊq¢fq\ÕP€á¶ËSW£°¾¹â e® ‡¤£¦¶ÞѸú¤ôÿ³÷îamœyžoUé#ë†Iƒsì3ÂEvÏéžIw;w·½^÷$='‰ÓqnŽ'Çö³›gž5“î3Ýxœ98Æøbwb.¶AØ`G8{{3ãžÄÉlFIÆábKÅyߪ’TU* %TUú}m@!¡ª·êS¿û÷Çd 8]*IŽù™´E-}Õ¢.ÐõvÉ+•—rü/A£ÃmÛž÷gT;ûÎÓçÃÆ6Å’q< 7¼Ú6³Øoa66àè}E´7”!:6mÚ° {'±Ô>•㯅äàU¸Y:Ïiâ©zê© ,:Ή\]Ûv(x[(´¢ú©ƒ@r“¸”ë²÷÷µ”£ ™L²”0¤q®'Ü8þNÂe"âÇ_à“ãÅo eVtŒŠþÚq8hA „KíS9øsÙŒ*ln¼€¾é欮óþÁ §_xŽ3:„ý ¯){c(±¢ú©ƒ@ò“Ú§r̼ÈϨ:Ç´ºÅ3ž:P ÞÍÝ8þB ¦Ñâùmï0?ÏÆñ’â7ÇJaÐÞ’£T>•ƒÈL gUçÎý—bë¯ç8÷wëÜ;/ŠßaªŸêÂ)¸ÿ°Y¶}­üÍ1*YÑAÉ6[ÜOÚ‚@rÚ§râã8£êÜYÆÜxî¹Ç…çÿŠÇSÏ?ÿW\]vrctœ ´TWtd< eUtL‰þÌa8`A úÔ7"gæ¥mØèxþ)Ü·êéX¼àϸ=÷Ä Û¹ˆ92E¸Ý@½ùóOuŸ;wsVýµ:¶G˜@G¶,mMhoÉS**å ‡Kt–ñÅ󇬽Ð}îüÏùàO©ÂSaŸæL ëÜ;þˆøóÏýâ;9vÛK3jÙã’¤+:nA?uHžRO)Ç)=¦‡9žÇÝ` .óYè¾®s~+ãyn~Ç/^à…Ô»Ïnþ¹mÏý/õìréÖU2¬èX§òlqH±MÉQ¬7`œg:‰È±cÛ3O]x*0ŠÉß!÷ܹnÌ .K÷l®“xõ//×Ô´ÏieTtx…€Ó¬ƒ£’‹TQÊá-‹0[”Þ±íOñ :º¹>éïàáLϱY¸ÛžÛÖŤàn{^ØñÛ®©kŸ+£¢Cd «®{3¤`©¡”Ãm‰<뇫d>ûýQ]œ]Á4abãSÆño©ÌÞ`¤„Š ôSd{õ©ø©Sö®¯>irøórýcš1¹T,˜V#ÇデŽk*Üí£¹Ïè†ö† |¥ôRŽ+¡±ÞЬâ~óY\:ÏuåÏÀeâýÇŸ^ÐT÷š*w»ì[WA?uHÆRv)ǘTE›DˆÿZÀ†Àœ¸ êaÅMûcòtÏuñcÛ^úZ­;^zF‡U&“’ Ÿ:$k)¹”cHz@‘Ä#¿~‰ƒE7…?ÇóE!Zø©Ø´*~VÕ_ϨwÇKÏè°È#Ðí AaDOlÜx¸ ×&XÊ-åvÔ‡iM1³#ãpñZ>÷ÂSçÎúËÙ«sÇŸW¹›Ê/éŠYtöýM£pœ*CžŽ•‡Gâ9U–áf×¶AaOB5«ÐÆ^é¤Òð•b_¿ÈfUë¤Û>í &RmÃÍHεùÝTª|×OÉ6ÐaW{/•G¹“zçÊŽ¦žo„ .§ƒT!¥n]nPa¯Ni&»ªû\׋rtšŒl{ŽélØü©mÛ¶½t- š±Ê´¢c Ú*òtSà¤Þ7“f}P0¿6±§`–r„æà¸ÉG?ƒÐñôùsLÕÆ6Æ;¼yMÏ?ÿÔñóçÎ]øÅs;’£‡w˜aä tˆ÷8H¡¶E8©á½Æz( M¨„×î¤öÆ Ž%sdÍìxîé ï üž8÷tÀâÀèxñé§_Ú1“,;?Ì0ò„¦e‹¼§ÐÞPšæŸÓãä­ò.Âk€æ¯ýa:f ‡ ]>™/mß&è)ÒµMP*þ\R8©‚Æ›üÐO]‘œÔ+ã‘öD{¯¾ªÄžˆ‰¤ÃÅ ¢4JcéŸv¼´mÛ6?.žþ^¹¢ÆL’íþ0´$a–§¨½aH <Õ!8©¯ß¬'!œ:`£'TJ*å—ƒK–QŸ*‘ÝqmÇK/bR =Å~}îÅ—v$5… t$(–UçèÀ“­Hp¬ß‡×hpÈHÊ)å˜- Ÿÿ_M}íÚŽµcǵ?¤“v¦µ¬ftˆÚ§A{C¥hDŽxœ×«JN§c¥Lå—ƒKX¡'Å­:ÄýÔ§`÷(CÓbpÄ!x}‚ãr’2J9$S1ÒBÞÍ^æK:2pÖ†ö†JU›±pÈIŠ(å ›ƒk‡‹ÒXs„<*: ½¡rMŽJ196ÅÚdÝ"xú Øæ –¨”C†'âñð9¸nØ1ÙÂòtXU0Z,Yå qV‹ñ+ˆ\U4lóÄJî¥Þð9¸et#M•È Ð1*zõqØ/ ÒH¼ÃGÁU%+ɼ”Ã>ú¦ÆÏ‹:£C¯<¸¦T”މÁQEÇñù —œK9"4¦‚óJLfFÇâÑYÜÞÜʽ1¾&G¤ãÊK2.帢Û˜ §±Ö¨t cÑÚÓB?u…kºJŽØ6?ï€@yiV®!Éð9¸:ÈÁ‡y—Ð@ÇýÔ® QjÕ¦˜>»0ˆr6wÂe—e#k/äà.ºÂ #_r A?uÅk$ž‡'ŽP-Dn9‡ÏÁµ€ó;nNÜ0r;´7T¾ŽÆ‚çÞ!ÎÄË(ˆÈá/ ß—*ƒyÃq”t ƒ\×  {ŽÔÁ^V¢¼›âÀ¦Áâ›ÖñÙ+2øƒÜáSAr|•°@‡p—7žP¤&ãxnß?k´0–[d5rpehì•Ä;®$Øé”v*TžÊÊx•Z\…t\Ù9(‚ÜHü}„á°7aW-‚3Œ|ú©«Bœ·jCÌ»IÑUà©’9,‰jl'Ö ‡ÅBÒ­«Èø:1gy/jƒC¹òŽ­Üpt$¾^º/ŽËEe%V²äJ¢C’ÞÍš; ŠÁI<3:Æ–&,”É1±³7 7.Hhøä‡Åâ Óº*®ðv3ä  ç1(œ¦GŽUn:6‘Nÿ:·” ¤¹An†¥²ÈºB-~ ƒ¶Z,ÖAØ× (ZÁpX™Y t€@ Ð<4¶1©"Z:ÐQ ’‡†HhL%;I·®Ê¸[%^0Vž SÑ´@ P¢i8,øEKt‹,†‘ƒ@ P†ÃB>‚E‡«è€MŠËEäm/\+‚¢Ð,4¦’7:d0Œ¤Âu…qû#üå›o¾ý†ÑuFß~ûí7ßÞ¼u (’ ‡•ÿ.JHë* …Fÿû_2Þÿã>F_=1ùˆÅÈØ7Ð7By×ñ{êÃaÁ.…™ÑᾃE„¾¥áb2ùÀÁ2bü_9, }<ü~P_ù¸¾¾éP=Ò¡¦vî¾™Ç݆­§(dLt´ÙÌZ-®#¦´ZsfQkG¬ÅÐ0Vš•äû|)O^© ,#Bk4ÛÐ2ú6lòi ™|T\á߯?8T¸Á £‘áGý¾yúb+H#5™ZBtngzjm51h3já°J‘d Ãâv%w3^F°i“WÞÿ8ˆŒ±™qbîAÿ!VbÑTÿ~ý!|sŒSÅÈÛnÓ‘¤µµßQ+ÝÛrpÁ,•¥sÏŸkiîp§oY“ãcö g~ _ù˜½ÍYï×svÇ¡Æú÷a›)EWl…lí ~«0IÍè°Gþº½8šeDÙ:àŠ2‰4uýúðððÇÃׯ»ÝîëׯrýúèOF=ŸŽ;>>þñðûƒƒƒˆ CCØo5T_?4ÛLºUc&¢i¬¹µàyøá°$¬™J¢¢CqG¿Œ¼Œ@ÉâÌûöÛo'oŽ};vó[ôi¹ùí·èŽoÑ­O®êA(¹>þ &ʧžqDØf ÐdEÌGTÑdlü‹3Ù´p•Í‹°Œ@ÊâÆÄØØMD‰1 D‘±›è‚ÒèõO<Ÿ2yT£èß'Ÿx>ñ\‡M&ÿËÊ"bþ*šßå" ‡UªÄÀ몺µË¤T±¦SâÇXãØðÿÔãñ\¿þoŒ ËÃÃóé'Ÿ| ›Læ¢k4ÄB¤mÇkl&a8¬R%j]5ni¶Œj` 'ƒ¾øâ‹/ê¸xñâ{ïµïk­).ÞßÓÓ³‡ öôôôž>p E«Õ™­yÅyv¤ööv˜/.s˜‰…Ê<e&Ô'ásp¡çªü5Å·u §ïheBz®úu Q¢š töô¼ñê«ÜwïRÍÕ=½½Ç«T¯;(¢ºú@;l3Y›EĈ*Šæ5fK¡®²Åëd,YHûîhE°‰Õ®ï âdÇ÷!p,­fÁqâ¶8ªðË~8NÃ6“±®G¾Nœ;Ôiž+†EÃpX5È¿¥'¥\7w&38&Ôî©êéél;öÄñÞž^Žüàèú‡c§{Ñ8É[{H`›ÉW­‘ a¶ Ùl™æHÎkjŽHGøá°Z«$ÓôŠÕb±^¡¸Œ ØeöR¤6±ºÁÑùÄ3Ï<³õXOÏÙÝœ«ªº³ýÙg¶>óGwõ6Þj(@ ™†&W…54lÕ'zmÙéóDuyA~†qhL•´Ëˆ`–·ŠÐçÞó ^F ¥ëêÓÏnEàx¢'ãØÿô3Xˆ%ç°«Šª¢Ÿ2•ׯ(8 «Þ³øSï0•å™áÛGŒë!7y—Q”Ð2’^™ÐsF­Öªwæ•gXêíå\UÆÿƒ»ëº ƒ£€oq p<û ´¥‘ãΜ4Kw‚h鉠êL©´Zs¸2®ÛáÂeƒ«…[F⋞žó~ràeDÌg®k#žyö8ÇoT¿qϳØsõÌ3Oá{z/8À‚CÃYèûíϼx rgd'TïRóžÎž9t¢@Âì0)ý"W`8¬Êõ¥Ô2ÒV÷νŒˆè—HÑúð%?6ŽõôbÏeoï?°^ª­O·wâ»p«êjü‰ùú$zÀË_û€òâ†Ä…¢ö@ïœ<x†¢Ã(S"=LPµhÂ(µŒ«b.õJ C ÉUªSÀKµõ tzÁàèí|е6žy¢—UÏÅ0xje~kÇ lB9ð!Ü ´z¢Õ‰Ì(Ý :«n?U(7ŒóXF¡Ñ#x«Ôê¥z‚¡FoOgÏ8— ýßp^2®ªjæs€ûŸeyþ*ùÈjoôöD¯Þ!ºf)3ÂÃa“kQ…=qXF ¥êŸ^ä°ñJ×ÉÖOÕþ4¦ÆÖgžîîéìdWh%\ØÕÁ/^ÎVyùkØ2Q¦ØÙ”9l0Ç|ÈÕb¦ÄëØ!WÍ ]çïxÙ`³ªÏKõÊן³ÁSÖK…Ãí=L¢ãè9}@B“¾_~üU2’8ñžÚ3÷!rJ8@͈?ÃaÕ«Ðn5{æ{ùs¼)¨çPéúØÁaãåqýßÉÎÎÎSOpؼÐÓsºçBgç… ïu^<}ñÝ÷öÕ¼·oÿ¾}-ûöÕ ûð—idÓrž®g¯ÁöL¼Ä…¾Ú= Pï ±Ÿ¡5ÄI |Ueà†€eéÐ’PC®b/Õ³âï¾8‰ÔÊF-žÙ~ú¤Hoì”Ð7¸aÑLÀ_¡ŽëúnªóÑ^,vŠ]Ü¡91£0V­×“že½½!xd§è„‚>Ê_/ÕöŽÓ~/ÕÁ“'%Á±KŽ™¶Ël^ðW%X¢Ó½­·ga:ÛÛ+Ê®2‡¾Ø¨ÅŸƒ ^ÝËhAW¬æ^F e)à¥òǵß=µù™pæÒé]ÜÛ/òÁÁÇ19ö†ïr¸ ÿßµ/`½°VÇËÿLV/q÷À¦»ÚÉydTŸWÓ!þX²6µšÕAÅ:1Orå838^ 4 âðG<Þzª“ãÞ¥%§"·ÇÝù[Ÿè)_x Ù4^Î9ÆYÿ›?βŴôO–0‹–Ñ<ºÌUËHAà ¯í i¡ÛŠñ¸@Än÷-]ºÔ48vñ[Üq4Úzm&ðšÏæ}€â¸s©8E8B¢p©¨bykQp©HùÇöñ§… áǽšÓsC˜”=ÃÕ¬{ýar@G<=U‘‹}χo*4_÷4øª’wÅ0ÊËH z©øó3üàè|#⸗ŽSo„æâr#9$Ñh„å¯0„(ù⸚c}Ä 2bÀÉËhÒR°Œ”,¿—êA[éS¼‡?­ ‘#{J²=np$Ïྲྀ•AÅ+ б6}ÜÄ+Ó£zc~Ä b@ª?4Þóe´–‘‚õµ?—J”ìô ?6î/ã@Çœàø„×û6—Põ"ì KÀ∟Fâ•‹ËuÉ…y É·Œ¤×Â@¬ZfÂ2R¦—J<ªïöÁžTŒ§Ê_ÈQ,ÕWï®Ú%.媶îàÌï(#›jâÐ^(\\Š·`Á2JR/ÕÖ!'ñ`áønAp<Ž“ÒÇ®›Ò¯Esø­œiC7â¦L^Ǻ$/) `žS2ˆâ8¿Ðá !È¡L/Õ˘xÈIœ×qäÕÝ~WŽÛOEÇ®]»> oßì`2ª¶¾Èƒb/m“að4·à†eÛ¼*XFÊQ rC"‰šŽ¿±›ŸUµ´äth{Ü]|nìz/ì Òv‡[_ùñÓd<š ÐAAouXFó˜Æe¤Tplå ôn§¬Å‘uúÔ©@{Ü]RaŽ}‘_™LŽÕË0Ÿ#^ºB…ú¦ bOà€åßæ)ÌÝ=½ Ì¡5W¼^gùó:`ƒ«ts,#r^˨³eO¦– ö„ËËHIàxñZ¸XÃiQ6.Ûªj©ÆrºSr ‡dÓ‘0†kvD›U55d× )éìCRÍk$ê}ïàˆ`H{B*ÈÑ G’:5ç2šßõ·fö„‰•Á2R8v„¿è-ã`ZŽhx¥ã¿Ù-MŽ(ÚàhG”à³ "È2ºÉøÃwz"ñgÅG|õYé3Afõ‰ÎÕT°Ï]5ŒáQ¿øË¨7à (sð‡`)Q¯DQÓ<üí«»ù•ãš ÁÝÒµãÿ;Š×¦ékÑu:t"‹t‡l3^6Œ¶g^G¼dÍGç¢:Aù]ݽ¼è¸&Ž$ujáËH-xœ‡à‡gDL«šWâ9.fcµh¾;ÉŽ?LÇÕœÚ"»¤óªþÕ+D—ŽKƒ½1§Í!Þ´Y"f^®ª³½’±N[àÂ/4C¬J•’^Fä‚ÁºôÎwFí2‡~bwŽ×v¾ä¤ÑÂÃ?Êi÷¾žIaŸÎ©+⦕(øe糂C;p„ïa=ZØâ¨6S„yOo¸V#Tðé2Â9V »ŒØœìÐeDò–II.#U¨ðËÈM‘½˜rÇfÑ|ÁsUñ[Ui,á-Ž]šŽ,TV vé\²F8â "ø$ÀÁy'̽a›ph!?™ÀQb1ôööTá˨s.p„_FàjH´ãba Žqzm· «Ê̪ í«¾+RÓ‘ –O»\¼Ñ¨HG|$ph ۉζ0‰zµþû{ù r)8ŪST˜ë^¼08ËèlÔË(ðà¦#¸dæ¸X.Šzr®ª¥š¬ÎàOví Œc}àH(8É#¾íç{qÂ0àÀ?ÛÛ[@'z%»™j3§XuŠ·‹÷–Qg/£ˆË¨—]FÑ[¢ed…ã:ÑŽ‹é´ØUÅÇ5š¼`Ë‘Ó’ÔØ¹s÷G1tUÁÿçý¬P"(A„?â{Y{‚" BÀ3©2{I Ž‚sóYF¸“ÿÙ^Ü_  ÕáÚÂ/³ëÏeTœ<)ÑrYKíÂñ“§¥CR#9¬+°CçocJ[‚#þ,ÿˆ¯öñ™ŠÞÐ(Ç -¡í”œå§XõƒCr±ÞÏ€thõÚB—‘È⿌ÀÕ H‹ãÖIq:®?8ÎÇ),/~ŧÇë1Ì „4ÿ¨5Ïàx¡ÿˆ÷_Xò¢àþ F§™ Nœ•Ä8’,ƺŒ˜es6Ž‚@2…¾'jpP`q¨Á¤ª“»üu÷.Åà(=Åû‘ä@Ž(šŽÌCP8‡$ ùy”½‘Žøj¶`#˜Ó#¸vä7BÜàd0_²ªÔŸUe“̱ øªªˈZpV¸ÿ— NìÛ÷Úß¼ö'puA‹C:ȱóv —/ÔE¶7Æ#ñ™ó³8ƒ´¢>¦fÑ(À‘LàȌᩎC´Œ¼pØ'ÚqqgIUœWªóôéÓï½÷^MM;ïÞ0]£k:µ ó@X…éÀsh£8âqú û½-\Œ=c³`x,¯:Ë §Xu*êeThY( •ÙBÇxð~ÿ¬¨¸dæ¸XHdá_NŠÉqŠgiÌ Žá¨K¤xM†¨^É:ìL8ç/ËÀ‹`: —U%pØÉ0½½gù“œ`v›ú—¯Éaµhõ_]p˨“¹£7`ÂöJ£W8uÜ®™9.baqH‹×w—àÆ{pÔ%Rż¡¶Ð2Gt5%HÀg.OôòL{`³÷CÛšB¯*uª0ÚeDuú;&û—Q/gÈF®ãèå-#ª\ 2s\,D_FŽ×vK—ŽÏ5Ë _ñ)Dät€"ÌzOÚÈ%¿øLÀ‰»xäŸS`‚:E·F¹ŒÂ|BbðR3Ø„î‰Qp} &a´¶¸òåý‚χè£SÒân«¾sçï`&R¼ã±Px”Ÿå»­8_Df0«Šuk“Âÿâ$hƒ.at›JÁÁKl¢ "´UçÆ1e‚ã…Üd?óùž0ë(tŨã(±kFãxã7^}àÕ×öœ>-ÇaB» ›0‘â‹6‡wN÷ôp½pyÝq :ÌÕ==s€ƒß*b6¸:5Á_FÜ¥ìEÀ_FL/ÜÀ2*ì-4R”¹º·'28zùËfŽ«_ˆ*ǹÀNó#ä§Þ.ã@‚M˜PñÈó=‘ÕÛ3ñƒšÐU=–5×2éíî¾pË’ºU!8~»›_:^ü^‹CÈŽ[° ©L ït,ÅŸ› ›[­²Í¹Œza¸ªümÕ¹Ù±Æö¹]U;w~ Û0‘ª‘ê;“£—¦ØÜª]FÄ<–ÑËHÍÇßîÞ͸T“}òd˜.‡<}›0‘áûzc~À÷ò§kÀæ†eË$ÇkC #±â{§m >²ÏGáb€ºñ¤YF½±\C"‡',#‚C課Áq*œ§j7€#±jç“TgŒ¯ùÉ0D;lìäXFDçYXF 9ôÐâ` ––Ó!àõWA!Gbå¥$jc%þu(å… ËèΗ´­Q§Åñ@` ÆxºóTè@qëØ„òq2Ä8®)¸R,#XF ÿ•Æ—¡1Žû¸INÆÓ'OI´Ç1 9+~×¢zÞþçH¹0ü“ Ñ—ŠÉ³ŒzãepжF¢yÝqy…ãŒÅ±ôb}Õ¡#Ñ{ЯkEÁ•"$è\‚et>nˈ†ëuˆÇàYì ÀÓÑT~Û0±âoÅ´z‹Ÿ e[ɵŒÎöÂ2E/å–gq,½qCó.¯Æ#,8þÛ0±ºÅkR L—¸Ä< x^ðHª}‘Âü¼Þ;]?5 ËH-Ë…×Épçn¡«ŠŠdq"oÂFL°ŠøG|f\ª}‰"ØÌÉ´ŒˆØ-#-£ÀT¨?òÚŠÊ8¸@m4®ªÝû`-$X“‚!_±juXÀR Za«]ô",#訮–Årû ^ët^CÜqDC¾'Ž]ó=6 z¯©ØtŒ8GR/#í‰˜äæ "㰌ԣ‹b‹ãUýŸFCñÁ¶YU„ @ïø¥u©wÝ5Û9®D9bèaÐNÃFV¿¦„ËHä8ƒeDÁ2RÞǹVUKCÁn"Ç—|ÞÙƒ¥wÝu×’%èÓ%ØÎñU+ëÌ*Aî=äÂÀ2ŠÅ2‚™õêoÐß﹬*ã` ^Œã÷&~d‡WMû¦>ÙÎ0ƒÑ’%KRSqÖíÌû§÷žÏ [89dŽñ2*„e¤Zpðgn¼èq⪠?c—aG{}¾›îí¥©w¥ø©qWjÆò{JÚî†ígŽPòNùjÁÓÁ…ä\F¸M/,#”N ŽûÇqhˆ¨À±óæ‰ÆÜÛí©È¾XÂXÈÎȸçžÒ‡ýÙ°ã­""†‹¢BšI"š.#êÀC¼Œ ýR•àè|CÜr„j=®¯ú.^¨ã_ƆYžz –=´ýá€~àˆÿ1/ô2DÆs~^~*ð0$¯³Š8Ñ+w'an¨ üŒ[!74T »Âåãî*Fȸ+ÈŒåë0,ž|˜§„íwyˆØØ½ÂÖ†HØ¸É£ë„ØæXè:²Q§ºWM:É?8Ž Ž¥<‹#´¯zàŽ¿aƒàË2ü®)Æ=ÅãF)X‹ VÑù¾z¼èšRa’{íe’ðpˆÁÁ·845<{ä·»w ¬àÍ×S­f„èÏ>+u$ s¶Ì<ש% Àˈ¿ŒzaÄ⵪:ù·xŒ?«Šà¹ª"ôUýá9ô$X‹"±É<ÿòÑS™°YaÍÛÜYF0¾IÅà·¡H8:Ãcß\à€Çâè¶Ø=@ÎïjñDȘa^lÒɺŒÎÃ2 ô]hŒã¾€«J`q¼nàÎßÌÅ ˆq,’&CŽXÃ<‚›]âKMtÀOÀFMÂed¯ã<–Ñ ‰e4 ¾j•é›01f‡¿ÙÎâøíö¹È€‹$1ô &£²·çÄžè ß…„*5‹¾bµX¬ƒËÈ,±Œ¢òQ¡eD‰•2Â2Rx]ÕO¾ÆÆ8î{à^®Œƒ(?ÍÀ]—ÀU%MHþ«r•ˆq6¨Š5ÌâuîhÈËÄ»æø‚†@6.MtàØy©tW€#¡ä ´{:ÃÛ=½ÕR¿ƒøoÁÁ âC¿”kOƒL‚Ð3û7K‚Òî kv àBñ0ËüTê[<"pì¦ãÅ¡àê­Þ8‡Á±b‹'¯Múð-8 vYcÀqÀFQ’¿aƒ€¦Šü)+oW[f籌x‹Ç«··÷@ô*"2a©Q_ˆ\ULpÜÏ !8v…©ßµ³~p<Çbªˆ£Ì‚=Nœð{£O8°Çf÷Ø0‰÷ƒV«Å ƒ¹/·N°³‡¢_F‘i«.£Ây/#šÀñÆîÀ8®Žƒïª:ÎÞØ¹sß\Ññ«°¡S­Dt"Éð?ktº¡†`#+ZC¢½m¿“eIP/®R‹õ AÞ¿ŒC£¡ˆb^åøé]a°E!TŽ/®<æ;<ÞÍ’…Æygp@(W³%âým‘<9Üù2‚tªdÇk¼àø½LVUÉɨúª¿ÙQ•‘z À±¸»5¼»**Iûf-üS ŒVªÆ-!;\æ¡w¸Œà°W§¼£_I¸ªîc'Ç.Õpœ Ž]Û+ÂBcžÏ±¶õbkdáW‹æ‘hüÔ ldEꊄƒÒîÁßÉ2n¨ðš”ÝnO½«ôc~7ªÀ8Ž¥\‡•÷Ó“RÄà¾n/“‚ÆòŒTfæø’%°Á[·}5ÚïT«/Ìo=ÜUJ<ðK¥vú`xÛµ•ZØ2‚1õêc=>ˆ ‘‚lK»¸0PÇ¡ƒc×®°&GH>îò{2Rñ,@Üo=£Ì}¶z4½GCÑ­°Ïâáw•â$È zªf#üÊ­,#ªh¶µÊ°1~¹<•;¥ß•jåMÜè ô'U‘ÖS|ƒ$,8vï»§R™ÉNwÝ•Zzùk0X¦É¢ùMúxpW)KndvÝø\ËhžVGÄeR1п±Áå,4ðI}YÆCÛjÇýõ÷2‡å”` ‡Ô'¶±zéÃo¸ôáRlhÜÅ)Õ>4ÐH°nÕ£>ܵ5SŸË.ñ;¥à®R†¤w¼;Še½ßÓXsËǽz4v©<•‰80î£{Ø™àÛ7Ÿ’!00Ph9%¨ ;Oä(½gYê¿{*µtð+X=ò¸`è°Es½HÙ:æz¦A©ßw•bš…ËîÁèZIuØ4±YF éRi*{F_’š*œÖWúpY}ÍÁ 8„1tº±œæ‘ã7¯†rc×.ôÿw¯—1ÉSÌÜñ»îÊ(þ¶¸œt»Ý¦­­Ýë£çý”¤ŸÜU ‘D.VI”6#íó¢eD̹Œ@jç=Z²ìž{íHøÈÃÿçÒúƒó‰ZU–Î9ºîþÝëÎåœwjÉ’”ŒíÌ‚¡!GÔ„9êµ¶š‘hViú”ÃÙBº"½ó†æ½Œ¤}ŸŲ́—H9B¦²4–¯›«Äû!{Éʬûî52ÇÑj8Í rˆ]U¯½þziƲ»‚!í7€²ÖdGM±Í¬e¬J«5ÛŠj®Ì«u©[úJÜU²—t®TGõ¹ì=ÙÑZ$\F­ W§îɲ4¤{ £{^¶»ª¨Ó'OIY»^{}oiFªŸLH ¡¸“ÉüeLÚÝî*™K: —°NÁ¦EÒº²‡ç¯Ò k»88¾{ç{ýoJâ }TËJ‡a¾hÒÈk—&ÊYÒY¸D) W{ ˆzxá*ÛßÞ¤^½ñßvþöo^/]ž´42Ö Ã5K’é î*…iÌDТƒƒÕºÖÖÖº‡22UKìÛ¡ <)Æ]E^M#KÝY.ÀqGz(údI0þ©ÌÜdUw´Z—¥ÂeáΦŸ[!S¡Á6¹4'ˆ$¸«£A2Y¸ ÇüTÎ6amŒÒaHžù»«Àm./Å* àˆ:ªñðòŒeœw*%å.h€8yK!»JþJ@®w¸Do-)ƒmŸ„àØþ0×­m!rWêòí0qp‡rtÅj±X¯Äq Rà®’¹ÜÒ½J½ñ[ã\â¯e6R£ôá? `©¥ƒÐâV§ îøÕÇÑ'Å€2W˜,Üxf¿#*@ŽäGéö{îIM]âïq»lÝ0\>*RãT´£îÈ+Å€2–7Y¸^=ï…àz3y,Ž ÆÌÀŸ–o¿~6›B5k‰r¸Ûê ¸«d{í mZãš…{…ÿRë`'(_ë=²}]D=²3#Õ~é¸fT²Ê¢' A1 L›^¸ó•ÀÈÑÁ^P‹ÀxLƒCpÒ°Ç÷µJ¡P†z‚²puó,p€d³—‰Å¼ð„b@¹)ln¼OBûÓ{RŒ¼Âë>K¼_oLÙU²RØ^¸qea¶V'¤‰FƒÇÿº²«d¥dáúU&|M¸t”"Z·ø]‰ »J6Jh/Ü1Q#EØ B4,:e,ÊÀè]%%¸®5kݹ,D""”³Ð»Jº’à^¸CЃ¥:¶l¬:r¶ƒLä3Í[½«®Ä÷Â"71¤Ty6¬gTsÇå!kÂÜÌл*ÁJ@/܉",а $©‰ÇÖsªrÈA£DâŽÜp“KÁ]µ(¶f²pyEØ ½:HÒ6Þ´> J8=È@¢+>ëâ¾:LLÜ¡8$‹æ/^¡ÃR5Ç ¬çi¶GÂ%ʇ\ô®à®Jš…Ë—(Ì2 »ª£|pƒí‘pÙœ ×» ÜUqU‚³pyòÐv4—6ñÁQ Û#Ñš"½ÙU‹¯Dgáò%B\0€BUÉÇzðg&Z¢–‰™¥îªEVâ³p#¬AØë PU À1 $±šÕÈ¢þ zW-®™™¨^¸a®šžR„®ªõ›`­€ƒ2&ìD ÙU‹gnÈ" —/k‚*PA±½ü9Z¹éhG|ž|ËzH«’Ñ®B8»zW-–ä‘…Ë× ”r¨@œ7iS\ÌcBp@CNçj6 .Šf­2ÉÂåÿMÐvD܆¯=qxzAÇú£°½ê´°ÈêR²«â/ùdáò%ºf…ëIåKxiO›â°ÿ„àØ<‘º":}ÜLðßÙUqßã”|²pyµ)…¥8nxₘXù¸²‘è<]žð?²«â+é,\Òè¿K4JŒ‚Ó‚ât„Z߇%"Ç$lñÄ)aýÔ#]KgWéÀ]ƒN½pè ÚŽ(Ü∻'iƒà<°É'«{=@vUܮ䖅ËÓ(LU¶¦§õ ñ6iÖ·Á&—ËÁ*—Iл*>’ÎÂ¥®È㯳ÀYEKâX¿)¯ÐÑq™È.ׂ]È®ŠŽKä—…k0AVa&=‰;š Ía”è~ê‘þ4È®Š±ä™…ËÓ´Q´ÚâîH:ÃÖÃ&O”Êe\uå-]ììªÛÌgµfóÈ©n‰,¢q8@¥MqïBopÈA7 Y'²ÄÍ]EêIR¯§½Þb1¦huÖeÖT‹Åd2Y ’$(’LOWÙ®–W/Üp;ÚŽ(y‰­BQSÛRÑÔ”»2Éd"I‚´Z³­999Ù$iHÏÌ$I5ìg¹gáò%j;e;Ê]¹(Ó2h.̱ Êä:T?+OÓïŽZ­oÞœ–©Eà@çIËÊœ•¹N§£¢ÁÑP×Ôà@ŸŽ¦ÆÆŠœ ŽT’,±fgçäÚ8Ò ™6’PÁn“{._¢¨Û8J£‰õ‹4ŸobäØ–Ž ðS%ì A§”–º«ÒÒôz½v©1;»¶6×Y[[‹, D„Ž dw8+RS)E”ÉTZê@?Å1¦ŠŠ SŠÅræÌc«•¿›Ý„ì³pyþ•p˜*F#Pœ—–Y?õù_4GpWÍÌø|†4=©ÓSpä:*êì˜Nºí¨p:pP$¢E˜LÎRt—;8þ.Å„Á±f­â÷ò¸$t)Ù†­ŠY” ¡„å[`ƒ¨U²ë§AÞùö®JO×°ë)ËùPnîJgnvNJ 21f©Ñd´¦ O¦ÖÚÚ†¦†½ès®£Q£Á™»"›$S4mmýýÍÍ=¦ì<«S@._ÐvDà€TYµJŽýÔ#hžîªôtƒÀ1‹e%Ù9ÙN'âFnÎÊ”\£¡Ñ—šî7™ŒFcJÖŠÚÚ¦¦††Š†ÜÚ:GC“#×™k]ࢻÜÀQ¥tp ʶnMÁYU€bj•UaM¬Ã¸«Âäúô$aµšîÏÎ6¥âŒ)ôÕt®Þ(É^‘ít8Ѧ†q45ÔÕ68j—•dei4$'ƒîLWfææzevùö £Y¶kÍ)AŒj,Ô*™öS 0ÙU¤dV)²7ôˆ&üáGÖ}Vô±Âáp:ÊM,9ö65Ô6ì­u4dgg³àÀ¦ i6œ?“™Y´™žÁÏG+ô@Ð)# —§ah;¢LMW‚§* dW`'RéaÙ:)‡}ZÚÊ\\Ú×èÌÎFàȪ¨««kh¨¨u8ë*rš 5Êœ¸ðÑ%5•àÑAPFcK Aed¤#e,Ç_TyfáòD‹ÚŽ@)‡RÄ–Q‡J%ã~ê‘þjIw•Tn•9}åJlIÔ50à0!nÔU4ÔÕ:euŽ dk4Õ¢ïkp-GNv*æF*Éèƒ" “±åm‚ÈHÃàXž†¢‚ë%›Ka‚¬2E8ª&`k¨T¥ÊŒAJN”nLAP¹¹NGéCØ›EPVkm-ŽgÔÕ9µÌ‘ºÆ:“5Ó@Âs‚#‡Fc´ºŒÏéÌÈÁÍHÐ=fƒAi.«!÷ £Qh;¢TrpaŽÓ`p$‰Á¡œË:‰ì*é&ªk6ìvüxŠXfªuÔÖíÝ[W‹¾Ö6Ôbp8pKC2à B£üÑhtº¶6’²f;Ù9¹9ø‘:³òzWÍZY¸Š8 EîµY8\#¯§íؘꕨ³ƒEAW¡î*isI¯×hrV:³)òøñÌÌ””»Ã™k·ZSR†60HŠÄµœÅáO¨â@¢Ñä®Ü[Ÿëhjhl¬u8œ n}(}iîG“ŠÈÂåkŒ|dyJ)¸Ì*¤Ð9Ù9$‡Í–’bÏu8í9ËL))ã“"¹X8SHãÉ9¬8VæÖ¿îÀàh¨uæ*>7wO)ƪƒ ² ,¯é4ŠÎ\¹«¤±‡C©5œiq¹º/`èýd ?7˜Û\ª`J¥Õfå51ùºMo548YY$a–£êûýàso.«Åb½¢ £Òª¨ÒT(I$ê§®Y§´7 pWY¤àãÒiÏœAàèfR¥(’B‚ ÖYøGR„^›ge°>**ά<Š0(ŠLä`j¦÷¼v*r Ï§5`Üérõ!bœÅ̦"xq üÓ¬¬ììÜ܆¿Ã–FcEmíŠez½ìœT?H…6Ðv’Ÿt*¸¢¢æÈ05è0l6—«¿¯¯¿àÁ€UÁ·8¾ƒÊO< ‡£ƒ£¡¡Ö±ÌàX\Ù¡í$7]—§ù.èa캟r““A°Žª~lqt_(.çÁð[¤Õ”cÏÉÁÞ)ßp:ì¹K,Á «øÀ÷¿'Ǭº:¬œ¦Œ P’È+Êg-UçÛ´—PƆ«¯»»¸(Öà'QâÈ‚!W,³Ûs³+˜ØFGNÎ’­VfÕþ؆ŠÁ!š1FBÛ(ÑRX?õ…È`Й) Atw¹úú]®‹ÕULm8A ª6&íV£1™ðü?‡£¾®¶®¡©Â™m2ê´%3kÃŽü@Õà×AÛ(Ñ*QX?õÈ¢3ZpMøñ.WßÀ€«¿¯ÿâ@ክù#QpÎè`Áátææ2åµ8jY¶Ñ¨Õ„AàH€FÕ¿FA %“*Š<¶JOÇH0™V,s"›c"ÓöqûÇí‡Û;Þ;ÔnÍ õÁD\£1%·?\‘ít6Ö9œµL·\ܥĚŠ+Ê-fsšO.7$>D.Ɔ:Á!OIMÁq ÉÉàPO].ýÄii,8L&»s%ŽööŽöƒßëh=dµêõD 4ÜĂØípÖÕ9u {qx®s™ƒ# ‘Cøåå2ˆv$ 8`‚,$')³ŸzZ¾\£±š²LGíÿhhhj*­ÈÊË^‘jZ–šeÍÈ2›­Vë Œ«Õd]qv62/jë¥á`ZåâÑä%%xh,E„9­ê±î YyÂZqÜõIàøÁ"ƒ£™‘Â÷æ´d¤rµW=ú¨NƒOìHئg.ä3™R—­@ÌКMÙÖe¦VÓŠ¬eÖVdgà€Fc©㣶bevöŠlôÌ Š0›«êîÎËZØZ‘;8h•€ClCÛPgÃÄH›r¶ñJN5ù*?ú‘^¿"› mãLÚ†ÚÿñwµÌÔ?\‘Q»w/†I“þÖV4T4à¹Nµ8 î´Þ¬’eDŒL‹{øÒÐà ½¼yÿÀ@¿«»;¿dI ûüñ$©9¸¿ °¼HÖàhæIÙ;ÚŽ€¢•§Š²e6EÜTÍ( …‚Y u Õ:ð´Ø†·8î]ÇôÂ7kßÂß"¦àÁNã¾2QHif·{û¥ÁÁòòýûq.VwWq^ÊöùŸDàhm-,,/p,ÎE$¥Î… ŠùJióÜ0 [#NºEÍ=uU¡J”"qå…FG’öRЦ¦ ‡³7542qŒ<0¶¶ö‘F“Ñ”¢Ñ58<Žë8òÆèÈÈÔM¯×çóz/ îoÀuƒ]]¶ÌTÆ¿Nê»[ú\ÍÍåÅÌZMœY‚ã¢å RË™Å;«ì*ÅbmoGW‚Í'­#ÝO="8ÒIŠÐk8ìuõMØ5U§i¼UшÁQáÌilÂñŒÆGRL)&£9:ꙚÂà˜ñÖ p¸08\6Û2z}Ëq—«ù@cqÐ3²ÇNЇ&È‚¢186Á±~#l¸HÔOX§ºwh0¤ Kn¢ž‚'ü$¹$%ØaD£1›qi`šÕbÉζX4š.—ËeΛðŒŽ0:=MÓCCZ]ËÛý¸7"ú”f&ƒ¹¥Ååêïon.ke!•°7ÞQuF$¥ïJh;ŠB#ëùjƒ ‰²ã©[ª{‡é†ôtƒÞj%08îbfÃÂq°FVCPVÜ!‘2+\i橯<ã£ã££Ó“h+ it--¸Ç†‡ÙŒ'qìoAÜè¯jnÅ-Op´¨ eªõª‚b¨8ÖÀ‰ûEœ:SU¦g|¾åi¤þÁµ6›mÍÚ‚‚4Ûj[f&®úËÌ,¬ºx1Íœ–áõÒ¾›³´/eÉÝé.×Ú‚ÆF-í¼DPÝ!Ò‡a1pѶ¶å ‹‘Íyù>I&°‚<¼£Š nø?õ÷+}GŠ*ŽòàðI¨MŽÊ Ø$1×°*ú©Ï%ê8Òm>X`³­}YX…/š-iyÞÛ>ß,zÿ))wÛ\.ÛƒÌL§«=8HÝ݈çÎ!XtØlÈþèwíw57ç1àH\ydp5 xpˆ'ÈBÛÐÜàX_5 Û$ÖJŽ~ê>Ê~°íŠËÎÿëëknÁEw×À@U5IŒŽ²™QÓÓ›ŸÔšÑ5z‹V˺³ÊËoy/5dûÁ‹}}Íoå—•>x¼e` ¿¿¥ÅbÑë)˜’–ÎãT|ô§Œæ‡6ú)~/ŠÛŽ@…(T8Öo‚uc%A?õ 8Ä µ 6˜AN--ˆ úîîêf‚eÀQnÆàx›’½Üë½t‰$¶\ìëGàhÞ_¶ùÁv3àhÆà (£&=ßÿZ3ñJ\8XW•JÀ1deÖžH|éÄF19ŽÁ~‰­ Çš4½ªÍfœ+…Ïž}.&5ª¯¥Y£),ìG÷íoYW6;u›žššõÑ^ïºú¾>çZa£ã«›ËRIÂh¶ÙÌæÌ4îò%·»¬Ì í¾Ð×W³Ÿ"ôúæj’ܼݦO>Ó¸búÇÿORðG ÏÖP4•ÀY9k¶ƒ©ØnK¬oˆ¾*ÇúØ71½: ’æ84š»º8p0äÀ®*Ž"Çþýe^ïÔììíóÞ>4T…À¡ÕbpàÖ¹SSËL$a6§ÛÌiiiZÃåËZm7²@ÔU$Y´™Ç_ÊgTŽah;"cMû (*=‰ýCŽ…R«b){²4ÍÈ0kYU \‘Ñ×}‘éÔ·¿fðMOOãb?üyp°f__ww`ð83˜\¯Ç1‘G‘YâñŽ•xúû[h4ÇïÖ™/_Æ5#X1Žr¼õÖ߉$F‡t³u!ƒ•WÔvœ×râÆ¹l{7‰Á©U1”jû©‡‚#ÍlæÀÑÇZX8¾£û7\šAà Ñ zÖ;{ip‚EN®B_ô A·…lÚ;>^T>2‚#%M÷ñ®n£®­MŽàP8|¥ÐvD¶â‡Ž&öO™ 196Áþ‰—Á¡âPã]KŒf6,Ž¡q¡‡µYËZZ‹._ž™¾|Ùju»=ÈÈv»qŒÃåb‡sCɹaOõ›‘]‚ ƒ¶ž]CáçdÜ^o£Ÿë †ôGãеꭷæBÇšŠ«RBÛ¹JX°Ø9ígUܤÖ~êË(%ŬeÓprÁÑÇ€£õòåiŽ´,·{ÂC³àp¹ºг8ü³e‘êËp¡ Šp±öþæfƒ#=.õã¾D ä¢/‰.òå—ƒÉ;©µŸzˆ~ôxjjs3k_0ŸÎâ. Oçpxaõ¡÷;®Œ+Ê}ƒ¦ÝÃEù[7oÀàð›,6(jhƒÇBÊË ×u´œ8¸Ÿ$SSÿâÑ8õ9”FÉ€ h;"Û‹³MòºÀ§‹ËaÅF³T²‚ñdJJ3×¢±9˜9αÂß]¨©>4Ô~™µ,nÜðù†ÝEùí7–Shøo 8°7jzº¸œdÁѲŸÇ÷ÓŸààk &ÈÊRÇäæ Ã>ŠIA«Q­z{>šNQØ9åwUáÛl×)Œƒ­_Jµ²ˆ°Zé™áa½^gÐê(¿«Šðÿ£ˆ²RÖÞðzGÜ$ÙÝm/6+¬*LK‹ÛŸ/ ŽPt$8ÄmG`‚¬,4"ö %¾ pºR˜Wx1±äÄýÔÕû^ÓÓ ¬· ÝdWu!p´î~ß´Œ fpàдZ-A‘|?/-Ã9SÓÓ³Þ‘RßÕ]Tn0®/,LËp,Ž`‚¬ 5Q)â†Æ`L@ `$jûCª8ʘŽ(pÎ_ÁÑÇOÊp¹úªö§¤Rfëccn·^‡Œ‹€“*`p01޲RƒÖbòÑ£Ag+8ÓâêJKËȈ+8ÂÁCޤX¸ÐvD~ ­›E“ µ 9bcqX’çÂͮӺúûzúû‚î*6© ƒc` fJ é·.ȱq·[§á¬ ÞÜ&8N •– ´Ï¨3×ÚÞ>ÓåÊÈȈ¯«j^àP¹Mn‡¶#r“8½~‹´› ÉMµ÷S¿¢Â÷hÍÊ+6è´:-¥3hµçÏôõ ôw¹.¸º[,F‚¢H‹ÙjµXÐô‹ÙlÐZSSRõúGC'!ïÌmoIIŠ…"‚‰Uee4=6V^>8¨%SRÆÆnN•䌸\]®W‘ä"½©ÿ‡Q(6þÓBŸ–1ãLBîîò7ßTÁÞ…¶#2’ 'TÕ$lÕJä"¶¨ðÎÊ*.6ðÜpDí™38£Êår=~ܨ# Š4§¥!p˜ :úœfÐ.³¦¤èõC´×;ãµ—X,\[u&¾¹ÞëbÀAQ©)ããS_åç\FÏØµv<Àñƒ÷a³CÕàM…qN U›ŒÆp€â({ôS'H›Íåè`„;© hNË´Ù´f‹et”f.Îg½ÌÄ>ú«Q·›"tš¼ ­æòå±±ñ±±)}ÃVRR’‡m“ò<`γÙÌf‹itÔǃÊDûÆÇ?p”^›–¦Ñ^¾<޹1…GÈ–”¤å!n¤!p”³9Xc_-ËNCàõäe|ü%Ç~J†àð©ÂV=Ó‰µï9›c”Ú©Z– ªGZIoéwá&†¸fœí…‹(rñб¡¡éi\Ãá½Í\/MŽæYq"®{˜ö—–^t»ÇƧfÙÙâØ*"Éîi¯gÔ^’cÇ÷o¤¨îî¾¾ÌL­!îoæïyâƒÃ ©ßS±³J%€rÒÄÈáM(üS·ìIÐÞ0ƒã4âÆ…8\Z[9pø¼3ØîÀà()!{é°Ûë¾RZ68ävÍNa¨Ð>œ}µ¹ƒƒöŽzJìÙ 8. R†øÏÜ58n+ÿZašŽ€@‹ªÁ¤ÈH1š33qûô³\+u²:Z Íx™$\/žä7ã³XØiâ>ßÔþL3¡Y/îg…¿&÷·o§™)Ò}[+—š(²««å¸&þñ¿{ÿHúwUkqðÇèàJŠ¿øÃÿ,ª=è̸Ɉ‹Á¨áÂí,8hïØ®G÷Ò¸[oޝÉñcnMaÄ “”Û=5eK#¨nl­\ºD’ç»ZZ R°k5‡4h×>zbÂ=R¢Ôx0›QÍÑ’i¾8ÀŽmêë»àŸ]®ýûSRF=LŒaönMb/%(Š([74”WRjv××—•–äqa#äèhj*A•­£}nwJêæ ®®4ó"¼Ï>“ÂFtàB‡:Àáµ7@w Ž«˜¹S ðÈ ÆÈw«ù]Íi¶‹Ù™ØOÅ‚£ejЇÇŒžšÅ.+Ü"¢Êʆ†¬yöån÷æíe›qÒ-ñáˆA0àД•Mùh·;5eÿ†.—yQÀñ÷±Çmuôe%V²düT /¡@Gø`uD§+V‹Å:¨öwi6t--܈ñ~úÇT^¸i&ðàSSÓÓyylgŠ0˜¿ºÉº±04°½A{ñÜ\En·3®-ô³ÁA’l=¤5âŽÏ> Zø6-‰ðèxIˆ7ß„® OXº3AAp¤ -ÇýàÀìèëß Ü×±ÁÚ³·¦o•äqÍE­alŒi{8‹ÝTÞŒ‰[·†6ãò8f18޵ê´mܯ‰ÄàðŰ0@ ŸÏ#è²µº¥€J3hµ®EÀèÑç8~Úlv#C‹­Žëî¡¡òr‚¤Ø^†ÔºÆË—GGo#{cl ÇÎþñ²{ø‘F’(±Úíxj v_MM‘„-Íœ¶­FnÁÁÇFôà¢ãÍ7 «£¢†ð°I@~p¤iµk úúü)¹Íæa7í›aÀqã[Þ¢O #pÌ2‰¹8+÷ò°Ûݸ$KòJíS\eÇWS$i³Y̋ң*àØËòù6…Ì.„ÒwÏ·<ýÉ'}>ƒN¯·Ùšà2@ì²ê¾PXXl÷zÇÆÆ=e¥aµ6Z§56¥¦n.+--+ÃmÖuZ’Bº1ʄʽ4==ÝxO Ô/N£‹Ïx¢ÿÝï/)ÆÆ/îhï›"ÁÒ|ôúPƒ 9(=}óf³^ŸiknfëÇ8º ËËq¯[ÏxYAZ³¶?ÒxH§ ÎOMm¬Gà8„ÉÀA"nle“s±•rh~\"Àá[8|(ôÀ¨” Éúþ÷¿ÿ½$|Ûùù˜ii-lë4ÍÍL˜£` ª$¾š*±kuõ›ÝH>š¦­V¿ÅA`?NPLSõKƒ>ú¶—­%ÇÁqŸO¯Ç?X´7òï>16æ,>6nAR„tL ë@‚wßO·—1`KÓê°cIƒÁÁc` pIÜüª¤D«]·yxØíA– næ3ƒbã18È– bt0à õÌ#ñ­ÄÉp-)üí3¯ï6ï§3pVù|#’àX¿®¬h°úÁ’îÍÛ2IÒ¢»ˆuánTÕÇ5;ì¶Zu­®¬ÜŸ[EûÆÆô‚úOê¹Ô\ŠÍ´ÂtÜ¢dÆËfTÙK1Võ4Hûnÿ»ð®_þRŒŽÈàð;¬’áÀûOw¡Þb1š´Z«u™Õb1Ýoµ ÈŠD÷§§Ã™„tDšÇ`Ë$58Ì™iÔuà‘Ý]]g‘½q®_°¦é4mi[èÇ€cŠÒø´Béõ¬åocr°©»·½¸³í›³Û±1’à·6'8hvàˆØêH†ÝŽ©¡Çè×YtF£VkZf5™,&£Õj1°q,pY òn’&G²;«X`ø•„ - mžhaG9õ÷ \tuµ´äçÍ5>æe„/è1>p‡\ìÒZjü“?¹ïþÌL“‘õ]éõØ*™½U_M±ÍIþÆÄàÀßóþ#‘Íq[­»ùÑåééyÅØ·¨ÑdYWdeg[­šÛ€U‹þ5TÔÖÖææš4HÈäÐû|‡ >PÔòlÜ´iãˆÚh;-MŽd'=8,k‹Z08p§ª‹.WËñâ<³™$ÇÆýà`'Z,„ž¤4:íÒ?ù÷Ý—™iLa]V3LU²L¦¦ìådâí¹Áqòo¼q’öMÓÉpa½üQŽ<Ü|MC­ÀàXa²æüiCEC&ú¨¨uäÚ7¬ 8(ƒã GÔ¢=UìµRuÝG¤R«ÚÀMÅÇœ„áÑG Bó`AK‹‹©/,$¨ÌÌ‹z½ÁrÕƒçmà4[øözËÊ-f«õu¤}Hõõ™÷8HN^¾Ì¦âNO—:qæ•,Þ¿ü¥„Åñ{¤7ÿûwßUïÎ}rsšÙlFÄ',çÊÜ•N§ÃÑàhØÛÔ„¾ÖU ›£±"ge ’É„ÌIr²Errrìe0dfjI(:nð¢Èª#ÇôÆPp$w 9€-ùåËõ¦£Ë…“q )"3³ã"‡Á3â£ggqä‚GiYŽ}è_ý¾½{³LL19±ÝŽYg)¡püž“šÁ±ùÉ´L”Å‚¸‘ëÌuT8jê8êjMÙ9,8p‚œuúGêÓ 8s•f7ñ®Ê«ÔW!ç©WUxGU’ZXEh4……-Çûû÷·¶´œ930pð E56b à0)‚‡wì+d·Ž˜Íqß}{‘Íñæÿ»oŸ1—v ^¦wVI IZÒdò¦XXH;ª|ôïƒzS•è ÓÒôzÞhÌήu8r(êìNFGEn…Ó™šŠö\ Ij(‚2¥:Kñ*°5bJ1[ΜY³˜ÕyµJíWãÓm"p$w»C‡5-¬jiqõ·li9ŽÀÑ~¤iÄÖ;wÜë›B&ù°%¢î¿[oîÛ÷;“‘qU]ÂòÎØK2MàðñÀ¡R›ƒC«5j²³µ¹NGn]"C®ÃYáÀÁà`Ó©p˜8 89**RLG´„îW9¨ñ]Nl€Gl$18°°‹I§3ÙÁN.Ww÷ûíxìô4†‡¿šƒö}5:ìÖëqnUýëûês¸x÷W¿ô¾V#ƒlªPtø•LàHO×°)˜]’]’“ãÌ]™ëÈÍÍÑ¥°•žFé~“Éh1š²VTä655ì­­hÈ­¨mhÈmÈÉ]a%I¦m¤¯¿¹¹° À06B#•ê|£¼0NeRgãFŽ$JeÇ—Ÿ8­’íëïr]èzÿ}RÏ‚ƒ­ Ÿfœ·£ãîa £ñõúú}N'®æÀõïët2Ȧpàe‹Á͉l¤\»Óž‹”“’‚“àЮK1MˆFcöýLRÕÞÚÚ܇£¡©ñeÅ ‚ÔèÚÚú8pÌ妒È9RéYÃë÷WIî>‡bl„‚£™Qm’åù‹¤¦°ËåêÂCdµZ½~|ll ;¢°Ý1ÃÌnšž¾t)-ÍdβZ,zý•+n7ެZ,²{Cá°áó½û®šÁa0„Õj2e/K1¥¤˜R³Ñ7÷g­@_J²WX+rgέ­CÜ`Rrkk³³³²–jH®±ŒÙp¦Ël++4DÜÐRÍœ6ªöíNtlZ¿é0ÔpD|àÈ/Ç 8UXØ…tÞµ¿V£×ÝcU 7¼ˆÓƒÛ3ÓtÚŒ ‹Y¯½Ò†ÁA‘ÙÈ GàȾߔmÁà@_M¦¬ûqÙúÈrT8'FCú×´‘ÃQÁ¡ñƒƒ4èΜÉ̬)÷ùfh("w"•ì ÓŽ’ 3ªÂ|IªÃfõòŒ4½^OÄñ–æf³¹$ ³K†‡g§XW•¿¡¡Û}éòå˃ƒYiØ™®Ñ)îúÑñî»êGZzîJÆ ¨ËÎÆà¨CjÀQqg}ÃÑÔÐÔäh¨À?wÔ:sTRRp¨œ36˜&–FcK Ifd¤#-ÏÀ_áŒ!Öä)nTÂÀŠ$ÍZÍ?I2›ÇU32ضè--o7›µ%V’LCà` ™*r¦3‡Û}yð8ò²pÃ\À!#™ÓWæb”È´¨Ã)§³®Ç2ê˜&NgNŽÉ”ŠÀÁõ>æd4Á‘–¶|9€#DÓU’Ý8&Àqƒ£|3&‡‰G&€C¶àÈu8q£bÊŠÀÑàÀv‡£ƒ§P90ð8® 4¸ÑÀ„F£Óµ]&(ÉÉY™‹i1 p1ÍÓÏ%°Qö†ordcÕÆc#ªÝáUŒ¡ÁSR/ƒåù%yxòn•ΜTðØ?ô}Y¹âßšz±‡0j(ûÊœŠ:~<33%Ånw"ŒX­))›û]Rè{vª ០Ìx¤ÐUAîʽõ¹¹M Ø©•ëÄ t@‹ ¼ÜøùlÒo–@½Ge›‚/zà`ÕàÀ™V%Åøª”q\Q 8ŒàY9ƒCƒÛ~pä89vSËAð&9¦¤PÜ8`’ð9ð.FF‡&7·¾!7·¡¡¢Ñáp8æLãöùøÍIdÎŽÃQèè–£GEwý¹@?DBwA[~‚TUõ“Ÿþ¤ ýûÉO߯w2? +õ_GÀ¡,1&"¥ÕµœqõuwSì¬_ÎjÄCþ&É»Mø¹¡Ófe51ã:pÒ®£"/‹ !Â!R¸©BuUœc&cvDŽ-G·l‰Œ-˜ÿU?yûí¹Ÿ H^bP„AæŒËÕ}dRmthã°¢x.*î+œ¼¼Æ¿œŽ¬,Špˆ´Eä¦lø$gÍV!vÈrÛÌqR?r8ß–Ÿ_œ?—ÅáGŒŸèËOßþéÛHG …É ÅÈ´¹ð(ùþ$0(Ö=Å„1Ø8GÐð$ãfY³³+rx>`†Ç2“^N* yÀM¢iéI³›&” Žüb[¾mKtà8ÌZØO…­ ,°8@ЇcÀfÃý*ûú|¤M±n)ŠeH ¶ÁdXY¸¿Un-nyˆ>ŽËÒâµÅÝU0•y?ݰ§ó¢üââ|lläåçå#ó8·Œ?¯DØSåÿÙÏ~ú3ÆAõöO±¹qøH4ž0X1 yÉžMP:í™3¸Óq__wwqq0üMÓo)Ì÷¦ev{NØ@ÈÈÉII1èÁEV@p¸©XM„Çú«2‡Äýô§<}Â|>àj@ÿ„>ü÷~ñ%ú÷Åçœ|sͤp€äŽŠÒ8úûû\ýÝÝE圹Apÿ¹p9ÉV2ö†Éj·çæ4T444"v8Ù9)wiµŽçÉM8þÛ1 ["²«JžXœº?ãs#ð -Ç Žà½dܶ@ É`Ö5‚8~Üåèïëï¿8P]Í·-8·à ’i…Ó™›‹[”`l dd:-ÉÄáj:â©r¶O›Â’C~A 0§n~`üÌçÚWyp€TD²‹‡ «ª9\P\xœ%‡&Ä ‡£ÖY_QרXÁ‘¢Ó”Û€€æTØ ‡ £á,ŽÏÄÖ†ÈÐø§PpÄæÕA ÅÕ‡ÒÓ™HÅ “u%:õ#8è3m···lïèhmµZY‹ƒk¤SL÷gg; eŽ:Ç^Ü1×éLµ’z’´˜Í¢n2ååzpZæR[8pÈoLI8‹ãÓÏDÞªÿ)ŠsHE8"‹p€ä)ƒ›&$ìl ·6[G{{"G{ë1«UÏi°…ÿFã²lÜ14ÔÕÖ:9,VL³Ù’&ôªÍ­‘0à8²8/?¦/ÂS÷“1…ì¢÷•• €ãýâüâü¢¢O鉑¶c‡ràø«¿ü/?üáÛFUŸÞcÌóqžË‘Ç>|lß@ x:˜o;²×>ú—oÔùζðU¹~íªÕ«W¯Í|͆¢ òó_½zÍšÕw¯^³zÕê5«V­YÍך5ùèßã…kÐó7 ›kÐ]è!kïÆZµjýc?ûú¥ï¡'þ9÷¿‡~òó‚ÝË ¢é¶…Å8¦'SÍq•Aã91UågY“c2ÈŒ ïaÞCXƒã踪ä­Ûû~Å€ãW¿Q78~^ùèÝw#h bC~¶"CbÍãù…è|wåã(#WÉܱ–A bÂņMoØðxåêǪ~Šô³ŸV®ñ£¢²òªV­ª¬p€¢2;:6ÌÇà˜ ÍÆªúùH4§ÿ‘-ë%ønŠæRQxê>æokXTÎp£¸¸ŒuU!냩´á#ûbú-~p0î­-œÍq•ù½Ñé£<3£-h‡Ð,PŽÍ8dnoüŠÓ¯ßT·ÅOìkVç?þøš|l?`<<Žþ!ްn+ÆaÁýÇ?e>!äî868ÖTVýìmLŽÇÁâþï­ªª|ì±Ç’´§cK‡ú¤/X›x½#?6LHýØ\çúH˜ˆJ4ƒß§î£‘¶¡÷닊°ÕQäfÀCk‹‹,®:4rôÌÀÐø?ÿêj[I~ÔËZGgÖ­i†G8œŒ`"eßHÛQŽ=Y[Ì¿€ãW*Œsð=Uùç뿇NýwT®Á±‹ EØäx㢨ÙÌØæX³jíj Ä‹µ 2î¾{æM6nü¯·lü¯[:*ölsTýtzÐtïÝ«ªòXUå.æñsµƒc’;•UÂD¿;ÙŠ#,;ªæp„MâÝâ]7æôŒ…œº'9px|ÿŠãáØ_e{sÃ{ ‹£×û¼>û´7F®^¥ý± f‘°ß9<Òq$àòùÃÓAƒ£Íà·>úUP*49§îñÑ7FG¯ßðܸqclìæØÍ›_Ýôýö&ú:†¾C_¿ýêæW7>EýdôÆ'7>q£_sÎÜžžæZ$Ð7§&'¾ûî»?~÷ÝçŸ{FÇ™ÅÿÁääçŸûÅØ¸ô««ÏÑÝ cÅÏÕ‘9cÞXý1ö÷ÖGgœº™óûQÖøìSdq”¿Ï&UaÏo=—cÅÅ/þê«LŽí;Gì©ùFUïAz}£¾iÖmE ŽI‡Ìõ‡_Áñ·ê‹KœºG‘?Æ?ñ`*ŒÞøàúè¿çƒ7®£7â…gôÄ–ñqô ¤Ñß}þÝwã£^zÖ;{ûö,†ÇÔØë““X£ &&ÑÃþ8119ƼʸºÁá92ßSèÎTô€ ï§n&6¾åcá|úY}9öUbbã8ÄQœÏô¬úŸlBíWÁ¾+ ‚ã½=œZ;‚÷1tÜã8”cqüú£d‡gtÔsc[×oàS>‚Äuô™ùcôƒëèÇØÒàÀ)ƒÁ1:NÓÞY¯w–ÇWS££“_ {ƒaÇøøžÑ‰/ðí/¿¼9ƾŠzÁAÓWÅqZHˆ·¼ë€éõ±c lñpçû‘c8Ìñþ§Ÿ!†aù ŽO§óók?ª),ZœÉTýþ–‡$saBG§²_>¾LŒc–Ûƒ¾™FöÅø8Íœ" eü»ÏÇ=7o2öÈè4ºàššºþÁw“¬½ñGÄ ÏøäþúÇ?NNL [ãÆ¾¡ZpLH\üBÂãX„ß›ˆð{Qì4þ©›>,8«w©)..BÜø´¼¸Ýfj:ÂE58À\¯),܃Áñ Ž79n°á;Á¸xÈÀ!{ý†OÕ…Ç޺ǕðÆ·_"xx<ã ÏÄ—ßMŽŽúhæŸo¡äôxÆnb°¸Ggg8¾ €ããl`†L|1΀c_—©ÓÇ$ÏA·"¸œ:fqlŠâUù§n.6îw0µqòÏ>ý7œ`U^ô™¿sÕ/ÀýÑGqž)¿ÅQÃYø— :¦¹è8ã² æ‰8”á«R_x\xêžØ²åç?ßòŽ¾Ú±zUå¦|âljϿÄùQU«o\¿Ü~ägoWMŒÞ˜ñÑž‘¶Ã?ÿù†Ímîïþ89ùçÆðª»ï^µºr‚¾9åöL¶½ý3<ŽÆ3ÑVYùøc}9‰É119ºׯÊG–“[¥® ê ãjpÄ[# s9Ñá-š}Æ;uû+,¸ŸÌ²¹åûô³á⢚â¢ú@ÇCGFüà(`8ñ.¯á;jxà`ˆ±eÂ#(!pÈYôßò£j ÁáÙ¸eã‘#ÓÇp÷cëúö—Ÿþ9¦GŽ ÃùwãÚŒ_|p}öÿoïìbÚ:ÓƒˆÝéí\Îå^ìMƒ6 ¬}Ñ•¶04˜4ž‹¦ÓJ+‘Ф¥&N댪bIƒnó ǃȺ¯Óhêì—5[ÜÑËåK^´*?ý©$IR¯^Ç‹&ÍÔ¶;j¦ïÚ8 –îü@‚¼˜,9ø\ù{kéåöîvCï·Š8®;ÖÓs¬'Poä…+R¦]\nç˜v‹Ç~À‘rêZnY>ú9½þ,íý^SêŸL ô™‡»úûioÆl<¢r±mL³ç,ÝF³‹îZ]õÈãFØÏ//êõýzƒçùˆ‰2‡ùX2{7éuúþþá`$¢üô÷Þo?P/=ÅË-BfŒv|á$Ë™c¦òÍJLjƪ¼Q°t+{¥2/')¯QÏçÚìÙ£Wù\­ííg¼ôÂr çÍcDWqŒœßÇHž8¸Í£b“Ǿ ZÏåñBqÌXhÄ1ÍNSqPE |¬ˆcBŽ8ºÈ‚OOCöE¼‹•!_yéïtßçøåUï‡u.Ì/éõú>½~Žn»JXÆh:ÎÓïôÈGÃ~³Ç“‡õ½÷´×‹8f÷Ï DuHÜUÓ¹%:_•¦Ï_ºgò7KùfhÄ1›ü‰pñøL~†Iú]wwGGG÷’×+*½€.×ÿº ê¸úLj„vrËd¯V¢Q)žó†O„8öùåq[•Ç Ä!‘‰6ØéÙßL1Š8Öå˜CùØ<8qíšûÚ ŰvîžoîÜ9†é7O¬Åâ^Îã‘óPº;^ƒÜM> %¤§‰aXÆJ$c÷×Γ«ä™ã³œ8”ok‰‹Ǿ¡ŽËã☕/Ý“"ž_\S¢ "޵ŒCÖî+ƒ ¹~#Ã0ú)ºø§Ò’ˆÃhf#² ‚žÌINºnê`"AÛ}–íd‚üÚÄà„Ùlæè0¬/²õÖDZ¥8nôIØOõŠˆCÞ+k‰ˆqÚù7=|e9ãÇS—zG.LGr‰*ïÝ»w¿ý]Gk;=Ö5—yФ•†Ál¡T9þ='Žø–Ê8ıç±ç…¶•z‡¤Œ9‹²8âT$âˆÅÖä(;¬bIw'CÅ!‹áišDŒÑhtEBÁàf•C§\­Ô8DŽÖ8ŒÁà­ÁA³y.îåxþ^¤þ:Ç‹æW ݃5^q¤77ËÆ¹åå¿=.Dùò»y|÷5Çq‘Ä–o¹R‚hTÞÝ;“†8ö“íõ´ä/ÝqE’{—•-¸rq<¶®ÔÆ3=±ØU³É¤7(g†xb˜DÌ‹8ôwä‹!y¤aœµX‡˜;ž!³Ñ¨3>xÄEêt:n~  M¯’8 úÆoa«8î~[z%)á@ô³é™âÊ8ıç)ìÔ©8”Úø y ðœ7»—Ž˜zßÒA˜îgC›IE8”¶ÊâXääËžîl‰ƒñÒ˹Æ!Y¬ìÐÐÜo;m‹‘.T§Óqsæ˜CiãÕ‡R½Îl„Ø(©bq”[n ­á¿ú™óc†nÚ$ˆc_‘ß=n«§UùK·/+þÏËSF&h˜qM)qÜ߇AÞk+›Âëbtz½‡žç´ìerµqÝ’’ª …xžfLìôpwW—^?ÅøÈõzGÄÇö±³hø{åġ엽—ÇFâ¸[æ{ …â ÕŽžñÂ!UÇþ  <>ê¯OqÌerå#Á Rט¸FÅ!ׯoå"WW—N×Mýó=V–aôúa¥À1,+CÉVuÓO< Éâ˜cLk—¾Kׯ@OïàÃû-âàÜ–ò¡hJ/Ýs›mãD9qlJk£¼:¢„MqŒ|02>ÞS"Qqì}œùâ°¥êR™³Ç#‰7â×6ŽØà ¾_¯»Ã‡ÃA~Ù¹Ïêõ:}Û’‡šƒ¡‰*9¼àsÞXṞ¤#Ô%QØÞ‘˺³g•½Y<Ÿù‰ÓûBœÏ‚á! ¢8„¼ART9q<~qЭ¹#ÇFÆiÄÁ‰Çþ 9lõÙ>ž'Ž$«ˆÃ¾䦕 ‡;[»¿60¨'x¨8‚.{`î'æÐî½wºuTmSÜ*±ˆ²¡J¿”ÙYeâCaI”$aƒ—ûÛt²ŠîÇÖx^~çn™f-¬üÃ¥=;v$â›1bÎ-ØNùµñÇ9qäÇÕ‹C¨:™ˆãüyqŒûÒ"ı)˜‘[?«òÄ¡ ±ZïèÚäÜ”R_[{ÐOâ «‹ž÷—QÂúÍJ†Ò#\özøEÅ‹\fkU×b(”H$Ò$.ñÎ2FÇÕH<‰e ñÔGúVÂáÃíí‡ï¹{'é›Åa~ ºTU›ÍYí\’þÍœ¸•©þ§ƒ½ÄFAÈá¬Cqø¦éÇs3ïö‘Þ¼™¨Š¹ÍfŽ¥£v¤5G8œkòSPºáeZÎP¼¡› z——•£;JsÐ+}Œˆcp"òˆˆãW>Óœ—'èÊâhmÝ{âHÎk?§¼Òâxœý^¼ñÃ÷ÅÚøË÷rE@ûA}üQý‰Ã-ì‹M:Z‹ ñF¦qã}}&Ö§œ0N÷Ö¦“£Ï̤êì4 ¹ÃÞÕ`P)lÐ*9çõÊ©)Æ–ÂòÖ«x‘^­÷­Ò“Ëé1å:VÍ/Žï­\•7>É¢à86údž”o|ÿ—bq|ÿƒXöÙqÔ Ò˜­lŠCreJã>F×FÄ1áÎn¿ý¸Oo4Z9IÌî"O§C‘)â*#³HB/·œ‰Bèî*/·ºJËä&fF ujŠ{Ýô«Ï…ƒËü2½ ‡övç¸0Tö@9¯¿toaÆåºre˜üß’÷É3[8ãªÀùóÌù‘ããçÇGÆÏ[*|„½-Ž‚éêõ2ë0·tóž6½þ6}0ô0rgÊí^Ïœ7‹ÅãO#ne¹ …”Bvr#ä[œ[ô%$I¤Pˆê"˜aÙKGŠ(=ãiAé(Wú¬“ï·Æ;{޲g2õ±%T1Ç•é+W\WfòűÅg*ŠcœÐ3~žQöTAû–üFÇêãmgné.¶éÛÚú:C$ãë¹ù"±õ8N§3‹¥¤è@”„§ét$ôh„š# ÁË=ã¢DÄæW•}ºúáx< sAOÍâxy‡“ËŠƒÅKl#ŽRœ9S¬Ž3gf¶¹Í8s^î‡8ö1?ŽÖß–ÜÜÒž3ôëô:­?ðÁû¹.ñxœ\ôðrY‚'k>/ÃÑK$’H„Y ’efUe%¨]B¡DòÎÔÔð,keû lòþýµ©=âˆp>÷Rv5•==¼¯o/ðœÄ‘ýtæ ¨<,##ãã.ˆcŸãü}¾9êbJî¦8†û º6½% Kä¼q?¯Òø˜‚¨‚¦ ²`ž5fõ!E^°8>…8ê–G…!‡c/ô‚Œ|ßaÔ‘/.ÌÑ‘çTòßäº-7O$¤l,¡ä£ä¦¾' Yy›ÉªDÂÃ)âÓ)ú¹'‰UqÄ=œX›8|}µuäUˆTú¶IqÍeú6ÜxžêÈq¼²ÉªÀîÿF©ÂßÈæÜIM^ºÏ£X,òù|ƒƒÊyãÊ_ýtÄ!ÃÒ±RV‹…þmµ;iCŸÑĘ ¦N†12Æ~ƒrýƤÓYzN ÑD?;4¤Ó™gõzùgY-yT÷‹ "ŽŠ¹*w…Ûm·±V¸Çq€8À³"Ùm{lÀzá(”ö&æ¯à:ä|pp ¢s_›aÈ*KUÄÁZè DŽ(ÃÔo0˜!“Édì4(×Éâ`t:fˆÎ^7)â` :“q` MÎRL•¿i¥È¡RKàÜ3ˆ€§Žâ'øõëG“²˜cl×Íáß"[õ¥Ž‚%|zšªÃlÎ*CUg˜Öõñÿ,,ÐêÆü…[7o~2Ù}¢¥E£…Šƒ¦ ¨>¤ÚhhøÙß¿þ«×qäÈkÍ*Z£VkµOÉפÒ42‘µ¡B¼2e²Ut¡öï^©Cp–þÆü)qûQ¾­­*uW×ädFó·n~99yâhK‹V §¨8¤Œ8$*¡447ÿê—ÄGš›iÜA£aC”Ò Y‰8ˆNðl™Gþ2½wÕöm¿˜ £l$ØÖg’Øñîmsñ㓓_ÒÊøü›oªÕo¹åÖj¸ÇIR"EUò×J§†[ZºtéÒ: .yf¯TêÅßÑ*ˆ $Ó½½´|.á©›oðýe2V»·5WŒÚË©£Š÷w;hŽ›œ¼)WÇ{z4š#GnÝ"âh¡âH§©èf[Aè=Õrðˆ8œ—œÎK—þëÒ믫èÞ**šÔzš|ë-„àioÒËEŽ]û•Çh¹Œs»3 i÷ÅÁjÍÏzz>™œŸ¿29ùçO>žÕ¨>üî“¢‡7ÑþŒPH”|¾–•ú_¿@Ôát8ÍMĪۋòo!œ8¡V·¶â)ŤJ«Ã¶‹û«VÊÖ_Æ~ÜþÖ¨4 D““7n\'âø3ÇU•æÃ·©82ý=:–ˆã Vý‹_\"aÇq¼Ö¬V“ˆ#+Ž“*UëAø³×nݘ¿yóË›_|qõµÖãI&•ú…²)W”¡%VÛÐÐÜ|á?œ—ÞzKé„o¾nÔ"IÞâÛm¶gl¿{ÎøËn¶W³]˜¶7577qܘŸ§âøæjV’˜$E"ö,iµMCó¥‹N'­i¨T¢$<ýú›¦F vS@c%WêÝ›š[¾Fn«òwê>M·Üj4ç~óóÏoÞohÒjÃáP‚ÆÙþpéi2ùõâ¡Ö¦ÆÃ‡Ôjï¸=:5÷ ’TPÉQú=¾Ý¿K +©|бm\¦£ãàA¢UÏy¢ EZm(ôð‰$'«òÄñU{kcã¡ÃTî;T*ÄÛ’*Sê°íš;•Ùòe«vøa{ûáÃ4¥RMN~ôQKˉ*Õ‰Ö¥¥tB©qȱ‡$‰ËžÅÅ¥ÅÛ·þ’VG›ðl€ªX+÷&·Ü!ì%%[•[¾qОðÉÉ *ŽC*õÑ£KK‰D:-·sÈãGDÑKıøÝíÛ¿-Ÿ«>y²·÷Ôðé÷{OQz{¾¦Ö4h[[ð €Rnk®R[pìÎÄ•R‡Ýîè;¼ÑNOëP©è¬[J/ǩӧsâ8¨R54´@PË*m¯ w¼ô¾À2Uòš~îî¿V)Õ3rÈ'8iNõ‚ü£•qÊòxY³ÿÊ´‘×væTwǯÒVe.=žÅ¡‚8à™¨˜¯ÊDŽÀË(yDýβÃߟ=”Á# /úm~ѦXÇ‹¬—o¬8*ú+€G öÚï §Úý+Ïý§ ‡}»ŸŃ{‹”s´Vlv»ÃáD£Y¥ì()T>=UÀ"ØkDýöÑÚå¡D,v»“8„H$UJ!©h à÷ûW¢©Üé~+ö*CT(`O†þ*×ñòÙ¬§;T¢ý‘ØÂá´ÖQÆÆì„±j¿¯ÔcܱCÅì@R<2wl“û²çÿ×äª`o»Ã¹Ëâ[IÙ°wTnø%¡àÇ?Âû#îÛqØ£¢Tò¤ðhÀ>!UEsÞóÖU8Ak ì+¶òܶYŽ9üJRj›ª`¿õ;m/TvG ·s*:†#PÐ¼Õ ©zŒ9V ¶ÛeÇü¸ë ®b‘hÀïØl ·Uhì+}£8 (:"ĉûê1¤¢+Ä!~§½L=]eE®³åDb³;¶lµ £;À«†üè÷+y­1yðaàQ´`ý߈ʔÜf[8ã} xcqÃÖ‹Ž܃*âGaÀ(8~Â8€c(ŒØAÀD€îYAeÀH!àP=vv@áôö1œ3 " Ű£8TOÑ©£Ü%*P|ꎋP» §7¨ž¢Â8NoP‘hQa§7¨DQçŸíî•(LTÙÐÁ "Ql¨°;6TØ…‰*;:ÆTd¥¨0q¨@Ô‰€êñ¢ó@õáFÑ€ªQ:ÿ”§(KE¼D€²Es i¢ §Œ(‹Ã¶E5 |À±EØQ -Þ°ãN°q8Ñù EgŒÛ®£ƒ@%R…á8lCÞlñܶCÊMSÇÁMª‹9h'‡Í™Â= :R+þî°»ü?©DeÅendstream endobj 54 0 obj << /Length 779 /Filter /FlateDecode >> stream xÚÿü   & (*- ', 200. ('&6/:'2-/S-M)>k>x*X,NG9cM'IrGVN3F8K+2oo3n&5UHR-QR8jmLk-Qp3gjlQI MK> >> endobj 50 0 obj << /Font << /F19 14 0 R /F20 17 0 R >> /XObject << /Im6 49 0 R >> /ProcSet [ /PDF /Text /ImageC /ImageI ] >> endobj 58 0 obj << /Length 1026 /Filter /FlateDecode >> stream xÚÅWMsÛ6½ëWà(ÎT0>€8ÖÓ4M&­ÛDí%ÍA²‰S‰´):iþ}wñAÂ’Ii&‡ŽÇC»ÀÞ[,Vœ0øãDj¢¬¡Lir˜1*ˆÿפݒ´ûþõŒ‡9FàÝ øÁ°@Ë‚Kjsc®_Oœr© ²`ÔZ-pæãìã'F6ñvæF%ù Fs[pr˜qe¨EGö³³?úa¹¦Rƒ}t-?‘Clúòd©…ä&ìNIEM.ÝÉn—³›Ÿ¹%ÂÂÀ,?) ÊàäD)+²‚,7çw,1/ÛU—-ø¼Êø¼©³OË·³WË$7’º )Â÷íš+M"p*¿ŸÓÅ WÐ0ü¥^)©.¬IN-µ%ò°Òò¼pÆ´í¶Î••®§¹ˆ£7o†üÔÀ&I²Ï¼ ¼°„ ø0óÒ&A4feŽÚÒœåêÅXÖ*8 ­©¶ N”‚bBpådGknéÝPuNO%.eÒä$]í 3:]´ì9Üòk&Šy‘&弪7% ”õ­ĸ«¡Ñ¡=Ÿïž ½>úÎ*vÞ–¾Ó‚á©®1Fëíé"B¹¹@Zâ5EZtÃSˆqÒ¦0Ò¦1‘´î[m¶áðÇn…$¶ÝÑÓøôðƒoÜ7u]Þ»ÛL]㧬e3oºÞ=­3ô!Ž[Izš¸Ôk‚¸Þ O"G‰›Ä쉻€ Ä=ƒû½m©«2ÑgLjPÑz"~"\C÷Õ„Gl Ôvm³ß—ß_ãºß|û¶­0z·åsºT @Ä1×¾ÏGE€' váʧ^S"D7d%a saEHá®ÿ_"ˆQ£óó„‰×”Ñ YQã"La"Lc¢)Ü»ÆeGÆ ¾÷R Õu Ç1 ‰8¤ŽƒëqFÌ>1¥<¸|ì¥ý6¬v’¡‚8Õx„ê"§RúçœéÄi‚èè…×£â+W}Yíû·1HÕ<85ÝX‹³†µÎ™ío¦˜k›ã±ZïõéÚUí71áW‚Õ™þ|ÿ.Z`ò™nP(³”È_*œN…:뼘à'«müÄgºI-¾€ ,8°oíóâƒS«”pu®&ÑåùÕ?›p¨<ßˬpu$‘½ ä6$Ž7àóÜašúq¬;Ï9f°øê¾úpZ¯{wÏv õ6­ÿü†–æKé®×:‘1¨Å˜=c:7PæBQ™œöºß+Ò2ª´±áGÐu¼¨Œ™6⻽_ʱ¸9Û¾QúúǸù Áû1endstream endobj 57 0 obj << /Type /Page /Contents 58 0 R /Resources 56 0 R /MediaBox [0 0 792 612] /Parent 60 0 R /Annots [ 59 0 R ] >> endobj 55 0 obj << /Type /XObject /Subtype /Image /Width 1593 /Height 612 /BitsPerComponent 8 /ColorSpace [/Indexed /DeviceRGB 255 61 0 R] /Length 58759 /Filter /FlateDecode >> stream xÚì½}Lç¾.jœE¥«»P"¨®X{„Æ3žq´ùØÐ†´ûÈ6ÖÂHé=j!$qv€¦\¬{…DìôX:6¶±´SÂ_ ]©‰”,)M€î@Vÿ< ë✵”Ó¤@­òÉNä–•¦%€ñæ#f†û~ÙØš„`à}žÏŒ‰gæ}æù}* ÅRÈ/X÷¾*E¾8ÕÿpZVžN\;¥(’¢*ª¬ÈÒâVªŠç"¿WÑTš“éWLAAA±© ª !‘¥p/ž¯Ã×é’Ó’"]·š-ÅVs±µ¸Øl¶žî?ý•Þ•áŽ× Ñ!š¾|„Žs¯8ˆw/ Ñ‚‚bSÉë_à9‹Ù‚¦þV¼|Ê?‘åˆl1ªªÕRXXhx=Ç1¼ r^0x§×ëÍz~w¡Õ ›#2<\DZ¢WŠ©â HF¨‘PèÆíPD¥_Å«`O®}ˆîi¬—ÜC€6äk»¿Ò‹"`àDŽá¸4>ٞƤ1;vð|:Çó^o±±è w=“0cÄ[·(oP$"·Ž;j¯««³Ûká¤Î~ìóã·"ô‹¡ xyÑÇ÷Ø`/ÇfTÙj¶rœŽç˜,Î ž0Þ]üû¢(fùD†Éâ³víàø]¼!PHfa±UŸi1–X¿ã»x8 ŠäP¡ãÇêì€2 aÔÕvÖÅ`?vü6U/DsM¦àûåšeMÙ&ùú5«°/8áEQßìtêtŒÎétz| ¾÷³¶óYi».+Z„²Coµ˜-™êiŒhc¬µqõrP$ n?å L—ã€þøü8Õ¿.Øï;à/û[\±dÎÉ}×¾*,)Ös à _y¡•*KçwêÈt P £KcÜ.+ üò;x@iŸ––%¤8³ÕÀñͱ° )+†mQP¼mÜ:hÂä!Š:G} ¶.öîêºÏéƒÅ t;z™êÅÃû|ذ›×óÐ¥qb'³Sày}Önßûüƒø•$=h¼^ƒà„fqGjË0Y>§N |ÖžÕƒxÞ`Ñ[CD‘žówPP¬çµ~ëXu÷ÕË]‡íŽE¦ø6P·Tç¨îéªs£ÔAA±2eÄ*tiH~ÞÂs<'œð ú]üÿ!6OMËpK þÈòœ‡Ûá-¥JÊT“WÇð€;3K…,FäD}¥7[yI).º>Nj _9Åú?&E>‡ö)Ǒګ€9U†ýJ ;Þåá¨í \EáVÔ×AA‡f¯ò€¼àö âhn˜–Nߟ֛ӑÜDÎÉé-zÿ ÔtFQ¦3¿C»…wóhšiÄ„ È‘éoXž>–gŽã0p«aB‰”5ÓKmUëŽ[GcÌp¥§0^:å‰xŨ‡´b?JEÅ"¦ÈH.MaF˜–ûõzŽÓ ƒ÷ù¾âÄ“S(B—˜™HÀ.šÆû-$yP§Ó‰"#êti<ÌóàwÂWÖ ¬ÄmGA±®ˆ|nób@ÉqÑAþÚÓSëˆ÷qz‡ÉŠãô›£ ˆy­eeìsVQ0ˆ¿}ßßìó9§$ìÚŽßc¿Nݯà÷¯ ò:Q`a¤}ê Ž7&H¡*úeS¬?BÇâQ5€¢Æ*GgàÏ1ž ¦«@ ‹¬°NÍU[a4€G”ÓDfœ!|pf±Ç´xæG=²/‰Vt5z\:a7ïO>À|qÿ$Vn7¤¹É„wŸ0™Ð´õŒ¤œnpêX¯À0,`=ýã‚ÁŠcÁ×KOźóF,ü6Ê ==jLÝî tƨë âVÔ£¥H(¶" KðÔÝŒ&sžÓhHnñ"dIäEŽEÎàõ9€aE³Êi¸ÈŠvt‡ã§waÉa1!ñød%âq:Yº:œHr@Õ!O Ÿ¦§€bq+Ž3ÐËU@=ˆ8ã§{qƒNø~èêè ØŠ Æ¢pS õhü¯’rŠÿª°PdÎÀX·ß+¬¸ðèÊ1‡Š­[‹«¦ñ‚:^Uw£K`‘c}¼Àó‚^ðZùèÆóô P$ oö¸ ¸« h¶ê \­úÍ»ä9³]Q9Å–ÃbÕ(ìÖ@Ž ,¾­f³>KO7ìüÊßø¥_ðþ8ÛsGDI$‹p¢¯[Æœ´êó»] ŒªÌÃqzƒÛm G Åzó†½ÞžHµ=Ãq1 ±rÔU÷ ½芯HRSG™ƒb+`¾Ÿ6ÿ.®SüÞLJ3¾>åº!3 §Ï46 ‚Áû@ò»§QItƒOB;ºzépÍ.ñ’#*6úʽ¢Í›|¾4F`¹*‘K缂Á”¹µ™–7¤H½q¤º»öH|ÜT=$‹¿ û”£ Rò޹'@"¹\C5Å–€ôi¨±û÷è¡?ìùõÍþ§`¢úÜÌébåx ú"+¿SzCR"¾fDòÉk8åÛ#*2t¡/ˆ^_Dyä÷7²>¿Ïëköz›|àÀ'Q~‡1Gþ•ñ'Àp4¯9Bæ§è© Xo„`®ÆÅ@u]œ×ù1þ|áv#â€ê£y>ÀBÏ¢¡ÊÞhw@9ý")¶uà ¸7æcÉ Èö²NˆÃ°“7[EÁ;¨HJ,EõÔ%—; OC«»±Õëeâ߉ s‚å|ÿ5UA â,,o%À@\¡Ð'øÕé?ÞABÞ¿NDZ(‡CÏí4˜Í2Íß HŠ›çý©¤öË?#™A|ã`öJ-ؤ£ÓC¯ì@nÄÔͤØ\˜&̰°€§½#ˆ¤ú¼(áB^hi£<ççDžc¸t.m''Òý’ÙˆÛÿùX”ç!ÿ.ëä—\~«¡Ð˜°¬àÁ)x¬ü:YVd}@Ȩn7jçÑü¾“¸4ÆT Ö⢈ÕDÏ Åúã8‘u@dºk˜.c¶è©u8GUÆQb¨ê¹RKÊ­×ÀšV±¸Ü[ôˤØDM&È••9•yŸTTVTÚ*˲Á˜)Ë®36[A~Ey~Nv yùù9嶪¼=U¶J[[EN~©­­ªÜ–““ŸW¿ˆè|^~n~ú—[æà‚ ø“ürðY•99(#“KÑ ù;µUQ¬;B‹• ÿ ËŒ´ãh©nÂ]ì‡f«š¨¡ŠPEmÜa1Áã(usPl&ÌDm`¯$ÐÖ†øÃVU…‰OÛl•9„6òà?ðc«oVUU´ÙÚàO9üS`¾(ÀKp6Rà8““ ¦9åDLJ/pfŒ[ Vk‰Ùˆµ¥Šõ…zl±ìmõÿ@ñRÈòÔIŒR=5D|é$3Hp8ŽT£ºÑJˆ¨øý>)6›ìPl„%àKEE”4ÐR¶Šª<Äyø%/H2€2à®¶ü÷ aDeà°d³ý±ê`9`(5r ÎÈÏǼ™£¼ª}Â{å•¥ùy9zCºÞl°-EEøoºN‹5P$ƒ¡ £ ù5z:íGÔè®ý3¡Ë1A¹QBG :¾QÉ¥Øl¼¡`’(+ÃÄqóHU^e4WU´Uä-Ú¨>Ê#¯ò4kÙl@k€MÊ![üCÌ>õ^é{嶪‚rðž­òùù¶œ‚$C>„HüäW”å¡Ï±䃟üÜtWè7‹¬¦ ¬5Š‹)qP¬#"G*^ÁÌp¹þHí•(s\Žj(j€Xðûî„WÇèåL±¹Ðç«DTQö‘ ÐF[EY™-F$•@$dçÅÌT@+|ŠÞÎÌ`kk«¨@–©l—²AI"må˜ÊË+*£œ‚X#èœÊм²*(XÚ«äåæqœÙpœ)MÌÅźªG]4l*è¬ë€³ÿ/R zjÔvÅH$±P õSl.HàÕb`Õéáqòobb|:î ø²0‚ðhäÑ”$Ç‚{ÕØ Ì—ee$†?#÷ÃÃããÃáa5ö¾²“D g-,)6Ç-¡ x ¨\à‰È¶gÏž²=q8°Âþ½¥öìß—ˆ{ÊöG×ïÝ“ˆ²ªå?«’žŠä·J=G p%48UÂy5~32ðKãÃÀ4†ÇÆ'ƇåhÉ2è‡!Ú? ÈKŽ? #v<Gˆ~˜Î…ÉÎá­…fó´šXÞŠ‚bˆÃö)ëK÷î‘GY”,öï;°oìß»?JKycÏ%Š ‹‰ÏôÒœŒîž¼*|ýNÙ1KW‡a<Ÿ˜žŽª’¹ 41^Ã5ŒûwBÁžÆÜƒélŠcªÌ'”ñðpy«ÑôA=îØì¡õF(Ö‘8à8€•Ã(u”íß¿ïe±_ÙRÁ±%ŠuÇü³ùùWw·ÉŠ»QÆîx!˜8ÊÊðõ{wo8ÆŠ`|pÆ0 ÷ÆPáDÁ ¸ÙSL€¦†‰«ï!6R…‡qù‘ðZ=66444642ë >„ ð§o Ì>Íñ½V£ñž,Gg´èÅz‡í3dš‚T±?&ö¾q<'8ìÛó)%ŠuÅh{ùÇ¥¥mmí_v×þG0IR®ãÅо{l`l ÆúƒéFöA0Ì☾7F6ª¨©ê)õáÛÐs!£B%Á±¾‡c}cÁQ°~hh4ˆ¬TÓX¡À5C}}á‘áñ‡Ccƒ£Á¡x¼›]–]d¬§\A±¾Ä±hLª*¼ã b¯*[%b„²—LÎî;€vøÏ‹¼Qºßž2ñN‰ƒb= v|ü#'7'm}ø=>ø@|€(«’L´ÌE²‡¹}ÅPh†PÇ, ŽÐÙå™ã§™ø)x™E”3ˆdfÐÇ šÞ¾¡ÄAñ&¤ÄãÉɹÛ©>^¥Ï^°gc3’×MõhñªÍfxù¾‹íGà¡èë<˜Ó=Påì²At/Mt ëü난üü‚ÜŒC`ëþ<[eNN,f•‘ñ.Z%G.Ú³è—I[yî{ù¹ß²èƒÄQ~+Ž!ÌJ‡úÁÔfSJŒ´œE²( î@¥pw‚pÂÌ$žüô« oÏ'3ñï…îÜQâ XK€œq‰ø-*_$l+ÇÇ/{ Þp9(|;„ùṃhПW•v  ªl¶òŒ(s”‹÷k‡ÛUÚPm‘œwvø!;–¹ºPU^ðhÛó ’cl°Æùþñ½óÈë‘—Ÿ_Ñ–‡¹}ÁÑÑ>0¥wE2Gäv ¡'‹ãÿÌìOO¦âtÆÌO/ (8îRâ XC<»Ù‘@—~uëÙWÆ $‡,%‚»~‰IQQy\[ö»y‡•ÞŠ¢XEe©`Mª¯¿ùæëìþÁ¾ìŒ ˜+õuï7ÿòaH?cQÉ6¨¬,ÿ‡o UÁ*XÙXØäÅx&8fÊmô2 H"⸅IjŽÙå­R‹ÄñRô1 5Ì%Š5ÁÂ@ÇÞçÆÿÑÕÇÀK}&rpàøYùÁƒº~ÛÊmcØÌ¤Lš¸yÈñpè|FF£a@0³U¨¾È׃#Ã#Êh0Xþ.óÛÚ¾yôhäÑ7ˆ+rÏC^@»¼x'§ à›‘0à’±§åÙ²dÑË–…C_zA½(ÖŸ8P\He»ºyø³=Ÿ}V÷îÝÛ·AÌüO.uî§ŸþvñÜÙ}û0üü§®sû÷=wñO?zøù,ŠÅý^ê‚ógÏý2Óµ¯tÏž=·îâÀ&ßJ«Älû«+‡É×&¿›LøîÉÉÇŠ£o¬o–‘ÊΆj`ll(;ÜK¶ñqUo„¼1>>hD~¸â½AŒ¼gƒ•®¾Á©~ 2r‰¥+ÿëG#áñ‘‘eÁÖ–]…<èÁ¼ @Ð鵨(‡Þ=I@£•8.$–µwꎟžÌ\BÄÑõ˹ýû÷ïÛ9=¾9K¢pÏî;û'¢AºÐò9´p g>ž@á¼7ïÞA¶ªIܺà%ŠÕa`50¿&Ä¡~/@Àˆ_Q¥¢pܾ1( òròFqxnlµdC°ÁJ†yƒáa´ßÃ<È ¹üÿ QL=ºTQi»Pþ/püTÙq Œ„¡m –ÔÍ…Þñó¹€8²Ý“è(UVÒ*Šõ'ޤ8>]Ì¿€Ì1ûdæLç0StŠøù\B.G6X‘µ¿€ þ¶ïÀ¾½ee7Áî¢C¾½ãXp PSÅê°²vèøµÝÚW&ŽÑ×¹{Ô vwØÊʲsÏâ¥CU¶¶ t•Ëí°nÎù0®4¥âtŒþ0` À6›­¼àPÁðÔ^%G^_x8 ûÊJ‘ ç}· °QvnFv^^÷¾A¤8ÀÝ#ÓUëMí8ûïà"q|Šä爴@UªÎ}ñË’zUg/!_ÇL×YB#¿œÝ÷OeÿyÏ œuû3x¨Ïnß½Ÿ&Ñg\)qP¬ ó¥+ÀÁÕñMùË× ‰¤‹ðõ;†¨¢ÏVš—›“;Ca³åUåeøñ(\P›ŸŸó,Ò76¨öbâxˆ,U¦åfäf¼{( ‡heÔÖ†B²úÇÆÃ2<îÃѾQpOf¿;„< e}lÁàC¬8þ¾õ½&(Ö•8d’ËýÙgõß&Ìqüw£Éág»¾éúæ,DoœëúæÜÙýÈ<õóÌ¢—cß/?Ÿ+%*ìn:9:l°ˆâ¨B‰ƒbUýx•qµk,8ÞÑ P‚(à里ììœÜ¼qè+}8–]•—]aU§Ã÷q7ØÃ%EÔ¯±¢ £ú…S$1#üèÑà¯ùö ‰¬zªb#XpèYeeNÆ»?À`¬ŒÊw=Š* Šõ%ŽIBÐ'^‰ã{8ð÷íÙóO°žÈÅŸa,ÕÏO¢¼ñËÌÌOÈÿ±ßþK?-z6œû·²8Þ¸}êÖíPD¾ÒÖ¡Râ X.­ÚW1ZúšŽDhqä*2rk\³¹¹ï=‡Nì¾¾  ¢}6÷âî~adÁ Ž)—p²ÆÐ8t?AÇÈ=ëá*•¶|d«*›S'P„r!'?#÷< ¹*G!ÁA”9Žˆƒ2Å:öÛn¢2!·1qÔÃòïËÊÊöØw.\û æ?á \Ì"]?Ç{9öíÝ[Vv8„Žtçö-t¨·ï„°‡£mR¡ÄA±:´­L/*;õl´½¼¼41Ž·üñjþ5Ž8” Œq:” S7òÆÇ!s ϰÙlmí°¡ÆyÔÃïë©h¾Ç·(µüPp…Q}ƒ W}ó(ñÛÊ‚‚ü2\Tdœ9 ““›ñPpdG.èWK+èõ@±ÞÄ¡T’hÜ8ûü'g;a,Õ¾±¬ Lçfqì'±º‹’‰s3³ÏpþÆä䨾s;ò¹ß/)”8(V‰ŽyãÛ—÷ŸM>èè_Íß É©ïháHnS±2È’““3< ‡û¾ìŒ¶¶6Û@hНmä¼÷ÍÓ‡$OðR[.2UB½ñ%æÂÆ´rhÖÊÁ’(œ"2ôô|NNŽÓÍE=ÇG±âø¨œ^ë®8°o|ÇÝÛwqÑÉàߢÁT8èö稡 çÿa‡ø¹™„À*°âç™™'3?ÁbU¡ÃÄ;jGõÞæ)qP¬+ú8.¼Å?¢È(k´HqTªŠ:4ŠbœrsŒ"PËHµµŽŒ ?zVÉ%ġܬ@EIúÂnNÿRçßí‚™èpÃü÷ú‘´È˜ŒÇðh~N4H÷í@ª,VUÑëb݉'XLÂJU¡P¨5‹Elq d‹‰ƒˆŠhÎø¹ø€Ü˜ä8÷3N*G«ÿt ôÿ‚Žö>ô 7J«Å³J¨oó¯¤(q(ª2ì‡ó¶Ê £ó}?@õx£&õ ää/£Ut*‘­*çûÁðà¿äÛH E™þÕ²ÊEÎŒÜdƒÎ+#y}D1E‰£B¥Å:Çl´?Fh´=ócüŒ[ÆÆ*vÅYª1œ‹Ó#3‹ ¿ÄJÎ@§Èþýû‡>…–ªv™Åê±LtÔÁŽ·ýWlOѤ€¡»Â¦ŒQõÁ\ôè…‡ø²²r” þ¹8Þ‹qr@Îik«¨ŒsûÂá òà F ¡+ãÝŒœÉ¨c|úÐG¤H•‚ý+ƒ˜8*i­*Šu'ì‚?ÇäãBñ·pøÿS\>8ÄŽq¶©¨áêOQ! ågۣŭfžÌÌ€ÝË¿ÿþ3ø J¯/ÇÁöÉuø$&*ŽÊò{ã#ã£ATØÖÖq¡²² ñåï BÞ:"'¿cg{€aúi;!ŽŠÊÊŠ X#ÇÀ99çLJÆP•Üìì™ÍÄúò\Ã;¤NÜ…V-¢8èÝC±þÄõcçø÷{öüŸ¥Ï=‰) ââ˜ú‰„Qu=Ñ…õÎ〩‚8çcÿþ˜i+êä¨.ƒ}Ç¿U)qP¼ÔrX[Ù1ºnÁ¨©˜8*²añÁ ò[Ø`=B÷Qkñ‘¯‘ãb`Qp„ŸÎæ(…õtsÏÃʸ£ƒïAÚÈ9B{ƒ@r´åä÷Å111iC~ï¡s}Þ?G)qP$ qtÄLU0çï3T57;ÛIrÉ1jæâ¾¸0*lš:ûM¼ë,"Ž‹`ÓÙÉŠ«ú÷BÅ1K‰ƒbÌtØÚo¾D ÓÀãõ´ïo¼‰#/'<<>DÑss'•ÉvDÙç>B~aèÏ-?‰c[šÔCÞÍ­¨hí Ž‡ÆúËm|’C::}‘ôÇ q¨Ê¬-Å^¡†Q‹yëL*°sEU‘Ìq˜ x{OYYé‹™ª.Å91~¾€MS?/9Üw’ɽÿtö›Å)€·HÕDR¤ŠÅó%Í]?Hþ¿u›F“¢ÍÈ(ËÍCøàP?Öò&ÏŸ?ÿýèPPqn‡¥¡<ê?þÐùÑgÓ#£qëÉtt †\ ‡q¬AðLW…b¯Fƒ8É]URSE’Ç3’7Þ6º{ﳸ¼q\£pO¬ãø ÊÿÛ»ïÜ/3?ÿr[¦°‡ãO˜Qþfö––ýçÏîÞ‰öƒ"Çø´òÂ<%ŠeEDyÌoñÉ·ÉúGÞ(ÂÓ~F‹œã`pÂù²ç ¤ŽÆ“r|?M$Ž1š;†è!88ô‚Ç…LJÃ1«(ÙUe‡Æ‚ƒAøáápxˆ:Ç)’„8WFñÙG¤PÕịjá`\æÄʸ°„òoüíìYâO’cÿþýeÁÅN‚XÁTÙF•5%ŽÉ›ß¶w\$0ž¸!1)±ÈÍ$ý;Í&<¼¡Å™ãˆ%açõ\x L¤Á‘‘‡CËblddd™Õƒƒãá±áðÄÄXp¬ÿÐùCŸ–UåfdŽAÄ¡„£yô±‹b½‰ûÆa\H´:îgÈP…Æü²]qýü–ÔTß×…"uÙ ¯š6+À¶Û1ÉUíÊZÇ|ÇÇŸÄ6{V®Oˆ ÅëáYÇ’*RŸ$ë“´Z‚§©€”qqÔÓy4®.¥‰‘ðØòÄ14>^fmpúM&&d@-‡2ÞÍ€«3r¿Ç!äXT-9’´_°:þ}9ôá©Á‡sª2=qí$îK,ËŠ‰ÛJÅs‘ß“=æÖ·FYÜÐ|ãU“•UUQÞ b¡¬ðÁ¥8âxò ºED±ÿìÙK(¢ŠðFW\/À} ÝM¨sX5¹–Ä1[º|&½h7–Úøv™³˜Ìü¯*2¤ í¢âÂÉÞß·Éè?Ùz¾‡FƇF¡ZÄë‘—ƒX¨FÂ}Á¥>ޱðø0 {°"ŒÌ…ÉO¥‡Æ¢aYð/ë£>ޤ†:m•"ºîÅÓàuøº`½.)Ò)«Ùb±˜-VK±Ùl=Ýú ÃuÌÊuñ!:DÓ—ÐqîcÑ4…’…8Ú‰k|¶½êŸ m|ûN(4 pEIýÏøÆãOž\"q·gÏa¹U!] ôs³¡ÛñÞñe-‰£r5ý|(’Lm,ß`) mUAÓ4º›ƒõ’F«MÑÄ™ª¢Í™†ÆÀAà£øÆ$XR}q=)@2~:†|$1ßÇØÐ¡{’ËËÈxÞ%?<d1Fþ¸GFn®–IV¹Ñÿž³˜­hêoÅVôSþ©ˆ¬F$‹Q‘­–B‹ÞÊñzŽcxAà ¼`0ðútž×›õ¼µÐZÒ?lŽ ^÷)J?ø0rq²(Žyìß@q!ß××¥šŒ– ™yÍO1Ú %r/ý÷®?ýéç'K×'b’«·ýà³µ$ŽÑÕu‚£H&¬Pê|ÕMùÞ Ž›‚hH0™~¯…ŠC£ÅĆ~ÜF<Ð}"’èë GGú°ö@õªà(; s<S•xGzpèáÓñá§ã÷€Ü‹(ヹ¹e•í³ÊÜÓH¢êQl4´$‰1‡'×~†üù1¢4”ˆ,˧¬_èE‘0p"—Åqé|“–Æ€Ï󜞷ðz½EÏjÙ½ë霄e‹úbKØÛ'ŽÉhÞ8ü¿¡±~vQaÌüôd*‘f ™,£Z¡;‹¾ñe-‰Ã¶r}íyzÝn ¬us¥7j}˜Ã7ìõL8Ý¥ß TÙá‡ã*º§£·²F)ãá‘á0šŒE€ñp8 ^Àü<•eì· “ÝU)<>>rcbBZÉH¶vzâM‹xN‘‰J%v'ü„”†ÙÊ‚`N0ˆ'DÁËëx(fùD~/fíÚDÏgò|z¦ÕbÕgZŒÅ…'# Œ!' qŒÚ )õÛq„fãXâ§WÁ“„ígî}ø0Žîý,¢®%q”¯º£E²Ø©V:¥ÉHýrÜ=+éõœ3'''?gx|AÇ@ä¾s é O ò€+–Ì9©ÿ;ën«ÅÂ1'4à$KçiEȈ"'ò¿Kc˜4ÆíÑé²²tºí|Ÿ•–µçÓÒNoàÌV=wÆG®ÂI^ocÕâÅ×Þ†kª£…ÐíÛ3?=yë‹< Å ''_RsLbÅòײŠF÷~QÖÔTõ+Äñ‚Ö 7c»VvPöXG¬tþÊ“4G6ÐL‰gnnìÒ”o+ÿ¨ ^ÊyeUUT¢&ãÑ—ô^sóák9òLäå ¹PY–ö«°Uäåä”çåådçåå•ÃÒ‡åp3[A¸++*«ÊÊQoΪòÊ¥ ×PÒÚ©u¡—X¬Â»wóz¨ 'v2œ—çxÝnßûüƒÐ$I÷ý‚W0ˆ^¯Ð,¦ý6K‡Ï£EÏge1z°Ï,z‹`ˆµ+Ë807YLU2¾gÞ!åp÷Î ­‚… ï„^Úd;zÜ!N’sû÷—"GH][âøveâhé…”^¥yƒÉD’ÖG%c—ç\æWÖåÌA’#'¿=tUتªÊmmUˆ1ò ðmIã½üò6[%dÌ൪ª¢¢ÊfËA6¯ü¼²²2Äà •/z %¯Mî²"à,€*¸'šN„BýŽb³aðÔ´ ·”dDópèíe 1‹áE6Kç|_'Ò²DFàxÀ9zóî¯€à –¨ä¸ë;dÅ.¾xß8ÀÐOOfci߸uøs¼1³’ÿcfרBaYg÷ïßwî@×/³!em‰cv•>ŽÇŸ,³‡ºEÞ>ÔåÎÝ'Ÿ\JÆÇǸp››õz—‹É‡­-:’·ÙB(ÈÏÎÍÁz$/1Ç{ˆ/*ª*Ï´Á•mmPiäå¼—““ˆ£²2JUŸâ©­¢­Ç ‹ð¾n¡%IÇR3ÝìO³wn'bö¥Ýã33“ˆkB¡™'±®¡ÈÇÊòÒg«0q}r“^ýo›8–9¶¤; Ò‡$…«çóNŸV®™ 9§û8òóóÚ Š(ÏÉÃ}¥­íy€6 òÿ’Æ{DpäìÍ!v¦²²*Då9mm¥ey€0rò€ É+Ë.û¼S±'½_•]†TIy(”8’'½~ôP±àö ñ7LË §ïOëÍ™Pn©!:9}¡Þ?(}Õ¬(Ó™Øýñt7¦™Æ„€"S½  ð@xpLj 8€ž³X¬J„äo¨½I“9žH¡™ÐÞ˜x¥È*,Rî„f‰.™ ÝQך8”K+ǯŽ=³Ÿl ÐMŽç¨¿=ù2Æåz bQ®OR1If00,›™Ã† O€€\¨°U!ËT^^A9$„?–çÙÀÚÊJÈ+€X*òÊÂÍÔ(+ËÎÉÎÉÉÍÏË/-ûè£rlž*Gá•åáiNA%Ž ð ¤N‘‘üÙšLM- †wëùt¨6Dï÷ífÅ“R|­¼TÑ’ ¹yiP§ãEȳCäo' 'våç%pr‡š]Âq†ªç¬S??÷Î Üýî;wo‡ ƒd2tçNä>ýe1z>³.a‚˜:n®hà*¥~ò·Œ%á¸IéÛnÛušd¹~Ú S}Yçïw¸ÜN/¹ßcƒ‚<-°¬ŽÓ1ÌÐeô†úàiÔ© §jØZ¢EÀŠ‚7ÅÑá@^28È’"Ñ+e`^’²€ 4a9À~+úý>ŸwJRd)1ýBÂÖ­……(ûÈ2 —äu¸ˆFÇð<uÇ Ö‰‚YwâXÑzU‹(% ÷ï?°—U_Dl¾ôÀþýd¶léªÞÌ3ÓèÍo;.T$VO>.ÁCëÀÇ4ã)íx–ÌO”Êâx>ýŸà ¸­Ùí¬Óírúc Ñm ÏshZ«ç—¼ƒß¼!Ö0ˆ+ð™€6®ã÷î.ÉY û-Ð+%©€Ÿ"Êéi|~Î>8³€2-¦Å3?ê¡uÉà´ÑÐâqé„Àé<ùovÿ üðàvƒÉ¼Üd»O˜píåVp¸3.–eAǰºÏ ÐØåµ’«Á×›´ÄQY¹dà/‹Â0¿oE,n·É¾q±=?ûxôæä GŸÉ(Ã<ƒô–xÛˆ–)ý6©³ýe\pHƒV+c '€çÁß²Y à ·«Q–,81ãGÿ’Ó‚1Áü}%xLQ4–äl‚ï>’§›½MpXð z£Ñlž0™Ñ2%|êœé: ,YúÎ=E¯”d‚•IniÆçÉs]½[w/Úíô.”®£XLEè1ÃãÏpGpê9 A Ÿ>9²îQ +ÇÁDÞ8@è 4N|,ƒ½{öì%MÇ$ǧÉb¥ÿ˜–*I"Ì´•lOöxèzc?Ú -€ŒðV„Î,†mt¹œîÖL#ruÎ7´<@QW×Ä݈nšüØ-Ro ¢J§¨ÎR$Ù%ÐäÁ3»­è…=ƒøù ÉÿN}-Ìi ¬ZÈñ_dÕ ‚_ðJJÄ‡Ý òÉkXhx|óêP ¢×Qùü¬Ïãóúš½Þ&Øää)´yÄù’Õ /€+m'/rB1r£ÉÁÛõ¹Œû€ =‡ ÔöÞ¸ìh'ÄQãpT“YL{' °žàx’™(o./:h8.ÅÊð76 i‰ãÌfR+@ê`ªÈ–þâ¶UaéäýX®NôyDŸA·Kl6x|~§h.2­Ì……´Þ͆€‰gvIŠýò_„½¬…ãw¦s¢¨7[ ‚÷GE–¤˜–ô˜ Kî7§¡ÕÝØêeYÑ1¬U'8ϯ‚Oô°Ç‹xÖ0Vó„¼Q*"ǰĨp±–€£+J‡Ô`¶èR¤öJnM &lÈêG!njFÐþð³®wkoXÁ½ÚÀ@±0&IS'OðžËw‰†üî]ML³Ï×àöÌæâz™jŒ'9•˜;"]+!C£º *½éêÁ$œðÂpìôU`륌X&‚® —ÛÀ‹ËåmôB°>ŸGçcu¬ˆŒ ˜~^d@lè9Qo1GÔ “Ïs úö:{wàêaG”7²tÕ9jp!põ(4`áùΨÈpØÛÿŠiÄ~,Y]{Ï{”¾t¯òXÎAåÀ3z+mÉ¡1ƒÌqiÅ'0“™ÃånðìjæO>knöy<&Z9˜Æé²D@<ŸÅÍákhqyNXŠÌÿ,G;ûÐܾäÄ4±¢©y½#ØUU?O˜Ñˆ®†…–& !x?'òÃé¹4À»ÓOHf}­øØÔ»åwY_|yßå÷ª€t',+xüâðz¬S§>*Ñà†V)¹YtBs¨Ó8vZ‹‹#Ó†ùoÞ”ÐU£ƒš˜¡ ¨ ûeLðzL‡c~:(:±êvRÿ': ¼Èç”8Ø6@#w·"{˜áˆ‘ŽÃêÑ/ãõ2i,«sµ4¸=p„â¢ëÊõ¢#NÛõ—ëcd਽BœàÝKÈ;hw ×z§f‘8€è¨¾R[g¿µ).¦ÇKë“\ µØ_,é:¾ØLe‡å"øÐw]¤¬!rBšÀ꟨Ó5géF§Eƒ÷Ä™Üïýâ+‹Éd)*6ê‹m‘©[")ÂÊN!~@…GRa./4å¨.”U¢D$ÜEXŠH‚Xƒçõ·3Ý`5{%š¥Bر± >pé|n GÁ?b‚v*øâÔãôö`u¬Î©KcAԉ͋qvÍNH==-=}çN½\ 'àÛ §‰_Dêu6"•Ñà„¼Ñènmnn–ž¡"²òÔÙòÈãñù<.§öÏ’•a¼ò)T,@™opøœBoà¬ÞÆ"‹ijqÑÒ‹T€MS]öEËXìÆ ]Pq\<²Hh«MÂêã Ö{Ýñ¸|¬—ƒS¡¾Øhä8^e‡üã úÌÌôÌL#¦LKñD0–"e 9´¢ySîß•º}ׇôÂIôùˆ#Ü„+IýÞ„Ãa‹®£ºÀ~'·#-ÛÉšÃi|Šý-'¡Jžuâ Ûðã /žvÿI<½Ö'+P\NÖÃþŽg^ðªsf Ê3•šÎ™ U ðÆ ‰tø,):vx:‚nùëÆ’Ž8žªAØ>F/ž$QÌı1!©Ëyþ4ÐT©/f®ÇziàLŒû€6‘s£5‰¦|Ä\%ä’w`ËåÖ± þÅb$Ò èeÀá­…%Eæ*TalÕ"O\޳E9ºH…²ÝöxÅqìö¦¹ŒÔ•»xÐøªçiÖ¶äKz¼Ùþ‡’0-™Gqa2Á)š³æ{0QËh ¡ÝhBŠƒ>”ö«Îë—¿ûÇÞÑDA™cñ¦‹„B·0n‡"o,E^a%ç‘©QZŒœÆ[G~ÔsК< ŠKqù€áV7ÊÕhPjšðQ2I ô _¹ÓébY6üÃF.¶éè÷±P³œ<œL/)oµq:G5q¦+GuÔ7ŽÃªbÉâv”÷·™\€¶•œÓèª%-ÝÜ=âæ’7JêK"EÆ"#¢¢ë‘èƒå œò¡DP‘;Iúõ˜9mŒ74Û©qÆ­ãÇŽÂQÄn¯ÅÃɱÏßzS#ʃÄgziÏœ"ŸW£¬¤Þ0ÑÒS.]ÏÀÐnMçNÏ‘âÅ}ÅHg"ÏF‹«ÁÙâG”!ù›­Èƒ°j 0Í_¢ƒL¹aB‡Ûéö5@¿¸àkÁf,·[ìW'8½¥Ðj,’‚zl$köl¸êú‘ãHt@sÔ•Ú„¨©žÀ’äB¬âëSÝÚTWòäŠÄA+\-ÁbCØO6§{\Æ9ää)Z™!ãš2)uÍ/^,!­X3â‡EÞÐl«ßòP$tüd <Ôv:ìчUû±ã¡µN“w3:5sØ’(øL™‰eÑ„;}«º´8È0;q2Þ˜ÈErcâ)1‚¿­Éélp¹<îÆÖ/wžÁ—€F‹þfIlDšBò8C1ÚMÊ;ZçuŠÛ·ià³CŠVM–ÛÀ¼ÁÀï,.¶÷$©˜\?’¼ñ*èpÔ÷®&81j¯F}ãuŽº‹€j"9Ž…6ÙÕüíJÄAû>'`¾òU»Ào4Ìã±=dBÁ‘cpÙÃ\Ûò#ÒÞ熩Ž÷[§Ï7þ¯¸+Ž84©[ü ?j·'Œ1öxÏiíçÇ×^wô†#è€D»†Èà5”`JJ„TšRš$e›Žñ©°™8c4ûá)ž›&ñ?€›ƒÁ¿ÕÕê+ D4x ~SX"_=ƒÛ~¥€Cƒƒ»=Œ©¼6ÑÂ͵Z­>Ýb5ë7ºU‘¼;pùhB¸m÷b‘‘º£9.Ö€g…£·6Ýõ¬¬PS×FÉ"î[šÜ»Üw´¹j´#Ë…úÏEsȨBxDi:ƒ ¡­Þîx+Å#">™6­,%Rã‰CÓ·•¯Ÿ[Çìuu !6õÚ„€›îêºÏ×þ©TŽŸö}_@®§MzÔåAT†{ 'ðéFî4r}†²ò(¼¥MIiÆÇlBj ðÌm¿Ž½&²¬¦‚­ÁÁœY:†Ðb©è–£Ñh¾± —‘ã°–aâY¬ëìI(2rñJ pµúèñM™àôlùP—(]¼È¢Wµ¹,U¤[4CC^š–x³“¸~9¸²e{qìÚÊ´QÝ}õòÅúxÅQàV­sTºþP·v )š¶‹Š°ˆOßà le#xÊ;@p4à²02Ô)šT.Íhf$Yª/‚ÛÉj£‹ýw·’Š&%á zŠˆ`Ú d£EC·w6~ù€PËð,%)èÓð'¤hv”ŒI&ÓfpyEŽ­½Üÿ8ð‡Ãh+äq­­=v|ÓæÅÊ£íÏ'ÎQºXäÖeó$÷n¶4I™ðƒ¿"VQVäÅ|ãXþ߯õnK0Ui´[õŠ‚öðÔáj\颮@µ#ž8jþŠ9×Lu46£3tÝTÍM:|¾ ÍÖ‚@Bl‹HŠÇ3ô%ãÓx½2Œ4aìG›³ ·»QAJJª¢øYèþnFÄ£Õ¦`–Zp1<`ϸÀ, -˰?nƒ¶)D,Xu šÑª’Rb܃iäø±úxäjbEFìø÷Ø­Íd¤Ž^ ¾ñ±lŸ“ÒÍT™…ÂzÐTäøÆŽ‹¶jnDuêdÓÿóR÷ý‡ Ä¡ùnK^;‘Ïáøá@Máú¸òx‰ÙaµÑÊFÇ×h¤Á‡Qƒ¸u9¨¼„ç:Mƒóq%«R?öFüoœ~0þšï£\>VdÔŠœiqG=Z$E ! ú@ ¥ê_ÝZ´ñ;, ² b"P4`”Í3˜ª¡ãÇìѼpp»Ó6ìöc·¶Bžù›m‹£âŒ»ˆÙeònƲ,jô'Õ“:ª¸Þ-‘²ò°u˜Mòsœ³ ž&ÇŽ­xíÜ:_I5ð×¨æ¨ žˆ/ZÞ­GãÏÑ5Ë…*-®ÂN­‚]#ôK€Ñ—ÁoîZð4´ú`[–L4pÛ/ݘ8´1 5ZmS¤3n7b ­&¦ó)ZLx³¬Rl¶ÈlcmÇ1ÖöÔ²£Î~ôØñ[[§t›<°Çšv(‹¸¹LüÀ&o|%¿`ýËýïw$2ÇÓ­(7©ö_Ä G=µñ’£6^Ùײµ("|RêÁ<”Mý(§bZ)äù´M€'ö‹GÈ4)/¸[œNWƒÓIÅL‘ V¡j#Ì6 áŠ(s@Õ¡Ýånlq{´©š´÷û¥>ã½íØ ¢EѺ; ƽ)M6¡[·Žÿâøñ[¡Ð–{ðž¼9ÐAË$âÛ爣üñ–þB¤—$Žï‰cË•¬ ‹3€ã¦?š#±¾Õq8uG¡%+–.¶¶IÆ~2T7:ñ”Täš@Æ'ÉRÌq;R°) ê„‘A2s¸Áít±:7ÌÏ#ôÐìniA]baRÏiÛâÕnG>ÆÙêv;Y§ó<™¡í)Ø7Î ;wë‹v`Í¡ÙMGŠ-çÌT´WâKa!E³•S9B ‰ûÔn±ZEgÜ»¨˜jç¢ò5m/ª~·T1ªÓÄØ¤a`á†x!zâ‘•°§ÁÝàѱ 6miáÛÛ\nh¨ji‘`™õû4 ‹|‰ˆï}7ëd]Í*ÛU°j›Æ`H7%‡ãjheŠ-¢ÂhéàU"1®j‹¥rÜB-ªãR5þ É¡ãrx@Ñ]—P ›ÔÇC»Ø¾Ñ‚x²„Ãcµª\RÂ0 ‹]ØZT» ë‘°›Ò± Dȉô!ÅÑÐ+U ¶bñäô™@Jøö}§÷K Pä1d–L}ˆ mjŠ`-2F ¯1 ü|u£–ÂûnÇŽí;~ àÕc+k¢qKÓ“ÿ²èÓlÝTŽ[u‰pØYüË Ôò'p5ZÖ¨u’ j7ÔÙ×.0þ¿˜äßàäÌ”±Ý…ŒÀ° šU¾;…ßö¹\N—Ëç|ç·`ìß–*‹(qC«u·BÅÑœ¦:"u›ðÈ}jI2Ç6Ñ/Áˆ[ _àQ¶½ƒÜ©E&= í†+¨8vµ>ؘ×3ÉiMé£÷öj0ó>J+·Ì“w|Û’Jj¦z$&k·PÉå[öú%Ì|=] eTaGj¨>ŽØ«qêøÎŽÚúº7RÝ%¿£E%F`>ž1Ëâui(‡O«‘Í™ ¬z能ÄNwƒ»B|8ü6¥ù8¾ÄÉ|Ûš[ܽ˜•FP9­f·,£h+¬585Pc,A–7â(Q6¬­jIF=<4@« ®p¨õ¹\^!µÚ…÷qw@m‘E•ôÑÜÈuæ‹ìdZíÆ<«ýÚ]æõ —®Ð¥œ2Å2Øj©‘„UµW13\®9R{¥‡àrt¦çÏ„K9â¨Æ:$ÐP§äØ·ã~àZ«àr²,ŽŽÝ–‚>gꥹ] .\¹Ðå|@J¢ÃjX| yG´©`Ä”·AG†f›¨Ó‰ï󱯰ÚÔ°'n¨ ‡ô”h2 ÍLJ%áÿHoìWÆ·+íýD± ¶Z*Çq{B¡‘hZx VN'<Ñ“ˆÀ•Ú#µÉüŸ¡¡*îkè߆{€°m›65Z ëQ¯E*îí†{§çî ‹8¢8 ¥éÛœ±l#•v5ÛÜ®FœbŽÚ5iÞÑ,:×±©J›²1‰c‰ÅUC3ÇWå›”ü Ms xñ³Ú&OåáfÓGb. œª¾¼”1¢ÄÑm¯&Ö«ž@Bk€×—×IÇë¤mޱM‰v ïß%!5BÂe1?446“–}ÑvMx‹Tm‹ËªÑF·ÇÅJP¶x–[Çs ŽÜ’ÇMr!™‡þe‹ŸôãØÎñ]Ô5¾¸´ø¶“„jp*ìÑ@î‘wœ'¡—}1l#¦jž-&£ÙxC![Ã/ƒ8U¢å`¡vÛ;°4îš5¾†(¥QU/­Ó•ãÖáîêššú‹ÑJé‡ÀöÀË2GÍ‘ÚúÃ5µö#€=`"ù{Ýk¤7!q(&Óï߉ÄE%oI§ ÔÚé1MtGR#ïÀ_Ôÿ5%åOÙö[4+©)Dh@¦Ù¶-µÉ™e0XŒ¦¢éÌ»ÊMJêö°Aêú÷âhB†Ê7¢‡#±¼¡Fó½¥_r|dn)Ëcë¤r¯…Uu¨ù+ Âíª+b‘U/µ±` \龨ÕÕý—@O ë¯‘‹HGV®gB×Ò.ý©Y€ß6~Õ‰æ±I.T£ªÁéÂ¥ª\¤B®Çåöø<¬Ó) ~§×çOI@À †%Äs#Â.†3…æâ¢ *Š#n,äÚØ Í7~åúM¥õ•^Ï. >›¢£ª:: Ó3ú&°eR9ÔcQßDíÕh¹t AHó¿ç½KV]íŠzÉ`®ÆQ÷:¶*2l£‰¤/äœ,Ã8Y–Ñ…ãŸþåX×ådq¹7bT¶ªÁÇúœâû~ã—¨m“Œ&+s€;½b¯ øOpæ"ã ô«,D)c™ØèguIyCZmdMðSÇ…®7FQ›‘òKôŒ¾l•TŽP\4TÏ"\éºX,Vˆ®Še•Co¹ÝþZ¸òXP™Fª¢È gYVpº"ã•”â$æ®×&@u a©TSÝ%‡Û-44zt¾? ™&¯„¨`Âd†ü± ^ÑpßoøJ0XŠƒRÈôÏèã¦}½¨½“lBÕRä¹â~V¯-½¡×æ1ëæÀä†7[wÐño[%•#®L•£#ð«òâÅv+lê²Ã4ò×*X%X”õŒÓÉ1^'”ÓMõ¨ˆÉdC¡“”Åu¹®@- §Ïéñ°‚·¡é 7lÃè0E¦ßƒ{äé„È‹§ý a§µ¸ ‘SXp47£‘U*¾ÍU½›Lp|HïgŠ(Ú¨ÿMb!ek‰?ÏÜ»ººˆ’±Táˆ^ûë5t’°#CδZX—“a Cç•–K—e%8­ÊÓÑ>î†F—ËÃ::–õéü'‰›‚˜¼ [‘"ã?ŠÞ&¿à¼–âbY‘àU# ¦ÞÍr’ å )V@B*<-¿öØ©êÑ8ÞpÔ¬FhD=#=W»»/vÔÁ[…“ƒŒáªîeÙlлþˆÃív² ƒ¼<Øàrº¿Õ zÁlÌœïa3VѽÄ(\#®ŽuªåÁÆ?£ê?Òò†ë~’ò¯š/§‚ã­`aIWŽMI0 ÷äŒÃ]Ý ¼*i$æq`ö@í ì«*:"K7ˆÍ„Õlàè㊃uBâh$A¶òT4RABËsïÓ2"Š4åiu»[›ug M'¯`5™Œ÷¦e$*–86¢KÖÊ‘F§—”7ì§7òÛÄl{[eer¶yº°”7h­Æ7„-ʇ½¦º³urúsÏë!ÍWWº:ëkíu¯Q­JFÉ{r¦ÞÀqœDz¬×Õx†¼™°5z˜"©Ô…3- ­nÛÇÕ$øýïébcÑÜIV¶FY·©àX?<#=ÊK’ïox®5T½!lþTõèá‹Wž×Qñ~òÜJ0pµ«æóUŒÓh—é^Ä ²Ù piœÈ0‡ 8Y§Û $d*A›ÝÀpÓ4ÎëxÞçÿÒï÷øt¢OÌ ¢°Ûš ýpKðùZÄw;¶oßñÃ&ð,‘É´¼áÛ„|p1µ.Ù®¥ÙRÚ7ý­aIWŽÍ§ìÔcö£5Õ]Xp^\ ÷ÅÀÕîÎÃ5µöU+ŽæFš–Î0[8H,øõЬÓãŸWkñ rpK‘3¬"üÑ–•Ås¢è}M>^lÞíñù½¢ÙdZQÅBI),\™ÉúȉNÝøv%å C«g¿E´ÇGº&Ùhѱ”7:èùz[²¦r³·øaì‡#Ï__6w‰‡¼;9Ž?ÔÕ­ž8"!- frpŒ ¿~ÖÕànéR"ó8”JY“¦Nž` :>‹çw¿o8ñ÷¾Ý¦ÙçkhôŠÅõòÊ#ã‹uÉ>ÜࢃÖS_GL&±%H]*8*éùzsØü©Çp0. ¦­©é€v«ÀjªW.wU×ÔÆ…U­²Êa4{ORTÄ i Ãq~Ôzó«)ëätssóÉ“œž·Š¼˜Æ±Y‚Ð$fñψ>Ÿ×Õâòœ°™ÿ¹Îe”ûñkÞ ñƒí®¹ MK"Shy÷ˆ$¶.ùÛöRÇø›Ä¦OåøŸ´žz’Xª’ÇE®.%ŽÇôt½Å¸Íw·/Íø¶;ìŽÃ¯DÝÕµuGÖ,sG‘ ¼\×€²ƒÓ @t¬Nð‰:]³Nǰ:Àˆ‚ȼþ3î?ø÷^ëW…&c¡¹Ø¨/´aT`Џd2áãÝaÉñ`ÙÔð%ÁsÚ ìèè_’ü7Moá·ˆŠçì.$‹éqâö==[os›=•ãyâ¨sØ«¯¾ª©ª»>&5V[«êy‹•z*ª h«â`|„(ð:Ö ²ŒpB4x½­gÎüøÀ`-6MÅf#`Œâb\Ú”³z©¼Œ%EÈ7rò% ù鼞¦ªdr‘Ï&üY6z²Þ06{*ÇsEµ5Ýq>ŽÀË:9:펵$ÌÁbžãõ'r€9vò‚h0ì9Q4Xõ¼ˆ À%'{¥ÅDùhÑ©—{ÞK]ÁªIÙ±1]êsõÔiµ‘·úTÿÉó̱7Y\ä•4óïmb³§r„b^ èwØk:.¯.§#Ð]Ïö£k1fÉA‹É¢/6þ'Žãù4ŽKç9=§7ff¦gf‹L&“ÑR2%§:É ¥¶L»4Ï!ucV”¥å ×—¸>O—& sÄKŽQz®Þøµ°$•c³uåPíQûTýéìÂAU«ÌìJð”[‹¿Ao6[Ìf=@¡Þʧ›õ}‘ÉÍRFã µ‡L•F;„èÄíucÉ+|ʵç‰C“²C¯—–7£÷ïÛų½Ë1G’ôg½ù 巈͞Êq ô‡¯àüñÀki?òºÅqŸ‡$ü‡d†a‚ò‚üƒ³E÷†Y5›Bˆ8Œ&äô¡¸7uÎXÿ Fæ¶/Ã1b×VhÔ˜\†8>¶%‰Áp ¢RÊo›=•yÇí]«Öñ³5qò[kð·‘ SYºQR_)2Ú¿E×ç°Qä²B¶AS\ôðUCYƵË1ÇŽùv©j7mµ¤×åË0G²D媓ß~K T½%lòTÔt¼+ð¼¿û%›Æ^Y”)nûê;Ç.¯7°¹J•âÆP'àL³ )j‹C§ÔhñCéù:ˆ/D߲̱Ñ2:>XRÞzÆ×ò…牃÷ØŠØä©êѺºú Š+‘Þu%ðR.q{Mu, +Љ“¬…‹c®¸îÂ1aº;øO!ÇÆ£–f44ÞÀ½Êï·N¡ÚˆÇÿëÕ?jZT$±«ŽÀåjpœš«_ƒ,ÔG\Vˆ«C‰ ¶{?ÑÜàª{õÃe™cdžií$¿CË& &—l§_ÉVÄ&OåPëjö:IæÀ/µW¯…ëpÆnðrùj ûóµùÛdÂRüŠhDÌ'r”Db´¢®R%ü²¬£c£dt, *N¥õÔ×s‰ 4ŒikJÏDûwÊfKå8~8й¤Ô”£3ðÒU­þê[^z{PPï•Úש"ãÞÿÏÞÛ€Eqçù¾UÕ/ m¿ánc^`ÎØ<2ÍL$h§³»wo²$³™ãêÌ×›=‰pgÍDóBÜ\õÙ3ÏÞUAsÎÝ™IÜ4œ]5Q$¾M¢Ð"/ šÆÜ;èÎŒ;“ÙÝYà9@Ë‹t—÷ÿ¯ªî®ª®jº¡›®ªþ}Q„iºê_õùÿÞ§þø—Ì¿WþÙúÏüá ¾ëíÿû†ôr,ƒQÉ@yM«Ú¦÷lê´!K!3¥ñRŽ/^¼ø’ˆ/vÎ\Žˆ_`KÆ·½ÔζdïlONÙ8£™?b#UÄZü'!«ƒùó›ÿú ó]ÿÛ¢˜3¢[½QýÔÓ«ðܤgÖAl†Jë¥õ}Qê­!G“n+×L#Ggg+çæzåÅ–V<•ã•mÛ“ÿKæx<)žºD:С|rÐ6­ôiÔŠÍq}-40Ïti½”c«Œ£ª³óû/É‚£5\4Øùým¡¾%¯l~eÛ¶­[=¢|7˜$—¾t Ã¦øŠŽ¨ö†pÕ¦]ƒ6¬ßpý.ˆŒ•Ö§rü•U\†Tgû‹§•8´ÑyáE&ªÁdò¾$쌻]ÅÇB¦¢Cé;´7¦{”¶V¾9ª82ÛºmÛ÷;ÅÁŽŽÐ‡­[^je‹;ÛàxmFÍC®¢CÑ'}TôÛŽÃE ¥]ZŸÊ±=ÊQŘí\ ²¼fÈK¡Tª žç‘cë u ¹ŠŽ)õ,P08@ HëS9f^ãgTaïÓ¦²cÛK¡’ðcí8ŽoG@-^ÝÊT˜wòò²’ÓP=­º&ÕºŠTpE‡¸Ÿ:´7” ÑTŽY­½¾á¶¶[Ï3ª¸Û0$^láâùj¸8°e3šKø?´DÀñsõéŠJ±‰Øe¢X>\° ¤õ©áø8BEgçÅﳅ䯆 ŽömÉ£jó¶cÝ¡šqLlt ÿS•äA镺ZWM‰~͸`A %Hë¥Á™×_Ùºu+²°¹ñ"WHþjk8áöÕªN®Úï•m\Šn¨~ý§öÎÎKœ³ê¯´q1ñQƒªW^¹¡ÉÓ.WÑ¡Tƒ£ ¦@ ’ºK9ƤfI„øoð8ÑÙyžÆÀ´èüþ«á<ÝÎó¡´]&÷µ/´zâeÓÊøí Ÿ:¤h©¹”£_ÒW/¨ùâõPl±¡ŠoT Z„©^º ȪÚúÊÖ¿šÑi]¥ŒŠho’µ–ŸÝðþ‡Óp Ò+õ–rŒÚ$7ÍÒ­)f¶‡c‚‘²¯¾ØîeøêQdŽtã ÇV74}æe*:”ÐÙCÜÞp®SuhèÃgלLåSœàFƒÂžôò[¥¥w¥ç¡ÊWŠ}{ån«ê죣ú©«QÇá¦þï9ÖÌÂ1O§D¥*8}”7Üsl–:^:ß"ðU=/ɶm¯½ôÒëÛg2åäHÅÍè€ö†ê48Öòoê#©yþS¤Ò#JØ_¡øËtÌ.‡ C>™í¯Wmô¹$l2²õõ™T40­¼@G)¡j™%zPpS_›’´§!Ás€¯*½7bUE"e:-a•Ü‹ïGüÓv<¦#TKþRsxLàVL™L;ÿJ›Ñq×»… H™úPpS_³!YOB8ƒƒžV©©”C.— l‰älÎÜØþ:“:…ô"ûï+¯½¾=󨥬´¸Ÿ:x²Õ¡Bp¬y?åp‹#½RO)ǽÙ xb¿5vÄÐ_ܸ±}û_amß~㟾ÈÜ®ŠšÑ!jŸí Õ¢A8Ö|˜üç8 àPÄ¥ ½ƒÒÁƒ7ìГbA+@AÑ6†š‚Ó£M‰Á‘‚àõûW’ÔQÊ!Ÿƒ«‡¼›*P:¯$µTlD¾È89jÑ118Ö&½„ü¹5©ÏÜÅ-U”rôÏ;‡eÌè€ö†ªÕôZ196$Ûd},EITÊqOy¿á¸|®Î_RްM3:ìÐÞPµŠrV%;ï \UÊ’ÒK9ò9¸¥0S4I’i]µ¸¾¢ÏDÏ>çEELu˜À¡,)¼”ßœ\мð¼¨3:Dq¬Bà¤*ƒcŸàH»”\Ê=6R÷•¤J¦uÕâÑY´ƒ!À ©.ÑϦÖä8é¸Ê’rK9è>Ù\7N“-™aä‹Öž¶ ú©«[Qòä6?ž€ãn‰J9”’“ÍÁ5@n*Ì»Â4VtÐw ŸºÚ5!"dž¤þtaå(î´Ë­ÈFÖ´|näà¦FÒÅùöE!G?ôSW½S Ž¡þlÐ|äWbCRù\8¿S¦2minho¨~ ØÿGríÁÏ^;ý2+ÎU£n)ÌN¡FÓÖºJ˜=·βEoàßܯ'ÕT¥×‚Å¡0=¡´B¿|c*HîO­dš»¤>Ð!<åUp&T©‰ÖŽ?›ºÀ;h~,çíöÈôoõäSQƒ›òµ@§©u•à¤SPÚ©zr¬Mv;©ëŽ«8EäŠMÿ޾S¥Wé™ÑÑýÔµAŽõ\b"é{šuâP9léjl'VÌá°`o,Фgt¤¸uÕ=Þ“Ú`‡ ^®[t0×êÈZhŽ«8•ºm¤ûZºýTRj¡ÃaA WZftŒQ 2{AJ¶f6LÀ±ñô™|cªÏàè,¢dö”Z~ zƒbïh®Ýk¤^ɇ¥Àç½ØºFI¶®J­-pÍn³Ûûà¶â ‡U”Fm F@Š‘ƒ ©Ò"ZfF H7)«@I»s K)@1SA“Ô4*= 4§ä‡ÃR¥àI/Ñ è@  ‡U°ä*:àÈ€’+v‹¸€ö– 8$?S)âzNGEHËp`>¢é@`öî½»÷î¡¿_2ºßÝüÕM¤½}ûÎØ—wîà’Ô5«øS$è€ênP¢ !BJ·oÿëп޼ùÉ'¿Æä¸ù«fÿÁïnß» vˆŠ· IW¬á°°×PŠd†‘û•¹¦@ÊUÈ´ºù¯79ýúÚÀ'<ÝÜÏéÈ‘#¡‡…«.M|rx•˪×ãlYJ¯·æ®:Ü4‘¤KŸî'`8¬*6Š9’äˆßæ '¯í /#Ro¶ºÐ2š„›y›Ð1ÿ'ºöÉÍkø=Rÿþ#Ì[]ˆø}íÍ;pìÔ£Á½¹zBT_A$Iè]{“Ð.fÔÃaÕr½K:l³ñ.#³äiÖçîÓ#Ó46.0)®±Ç‹üÁ¢v?fG]-‚F-ȬõÜ*š\z"–ô®¦¹0VM’l]5÷\ðÀ\ËH·ÀeRn_š7Ãï1>êŽì¯EØøGÄ‹¦ÄÇ‘ýŸÀ1S‹š\D ‡Õ€B'ÑàŸÇ2ŠÃvÇ„Ö=U¾Ž3Ï;ÖŽ(Àñ•8δÅmhØÃ_C»÷¼³Ž™ru84¬®"$—+×+BŽm4Ü‘ÍÁÕÃpX5™¦×ì6›ý=ÏeTT{‡ákZ¿ó=¿eóæÍÏ#ë‚sUéöœizyó–ÍÇÎtt´744ð«ÈŠÐç«­XÉû\{Nv Û²ƒ“ïäž"ë¼ü ホáˈ[Eè½ïâ¼—HízéeÄ-Gp0‡îàK›±Žvtt"‹£!Wˆ~*T™Bq}QC˜7|Ì»—4\òÝ‹Ær9¸Ð˜Jû˨CJhI/Š\èB¢YsuæM†›·<rU-1ÿá6ö±£Œ«jOC‘{^~„5)¹÷£\1´'W*­Öz[î¦b”{N€–—áo>xäèðí‘LÁ²B5 Fuc3§—ÛÑô¤=¼¼eËæ-›7¿„C¾KÈÂ(YU›_¾‡Nqšê]jÝ}¦c,’úÒO29¸ßJI-#ýßü–„ȵ¨Ÿ¿ÂÆÑ3¾&ÖKµå¥¦SøÓ쪊hzkØó«›7¿ñ qQ–¤.x}ƒoÎ 'BG{¬ô·%ϯ†Ãj[·­R˨#ù$СrhN!/ã¦b(á;ó"Ïq…ò]ƨ`Öaæ{¶ÏÀ!TÐFqÂ:ßëÙ.æÆéf0ÀpXM/£Éd/#3x«´ê¥zÞÇKÏtÙ²y ΰªºÈäLà‡/1Ø`¸Q&Ç–.7`Ÿ©¢/øb_Güò5D¥VZ¥"Y6‰á°pô5£»QËHWœÀ*êèŒsÔª_„¼Tož?ÕÁØ­/! p¼ØŽ!âcÙqFè©b-­òÆp ¢¨D˜Ü3‰Éõ#\ñ¸ª`8¬¶—ÑÅ”,#ʽTo~ñ?ϰig^dìˆ-/·vž9ƒ9Ãx¯z÷Œ&ƒ?c3ø«$qâ=µ{îK<ê–ÐG"~? ‡Í eDîöu$*_ÔshÕ­±}sÄbøüÌ©3gNŸ~ž{èꥳgÏt\ê8séÒÇg.Ÿ½|ùÀ½8ˆÞøøôîþhÙ´œ§ ò«” q¡¯þdgGâòûG¹h ‡Í eÔ1E/#ðikÂKõ:wÏÿ9¾|~ éðËlØ¢êì)‘ÞÙ!ÐNæý—¸ ZÄ_Ë"ͺ)ö/øæuÁû|gÄ.î蜘Q«ÕýäPÔ2šß:ê/# R«T/ú‹—j;»Z~wêÔÙP.Uã©Ss€ƒÕ¯ÙŸõó×Á_¥‰®S—/¶KJV|>QZŒ5úÉB Õ)«±[ƒxuÌ_QË6—*×Í¢¸öåÓ¹‡^<{J;%Àñ®ÈçµüUé”È3]´€ ¾ÃgÓ?M—ºm÷5ðRÁ2Š8T­Ÿ¿ÌÝé>‘׸‡69%¥³ÿ}G4:þö ®¿=óäW¥[ׄn]ðQ6ŒäÉ &‘˜$,#­(œKµ=¼[üâ 6¸ñÊKgOIƒC‚;þûöH`ƒóWmys6Jð0¸:(Ÿi.gHƒº+Ü0¸:¸Š|zXFZòR½Ê·èPz•DT< ^P<üñöͯ†@û«¶9Ò¡ÃÉåF‡ï c2O{…qñ/£Ñ2‚9ä*ÕœSiË/#Ž«×C=GšÂœøè²ˆR1ŽWp©à€ÈlyB‹¯{‚ÕÇMJXˆN }_÷à gÜ2’\—Lî.£Y8È*T`ûÎK6 !ÇÕæ°› ŽýêßÌ Žû™ŸöZ¿„PGº$iR';’ Gê4™º²-.ÌAAouXFlŠÝ Ú‹°ŒÔ Ž-L[*)}ªãxg—ÀU•wöôép{ÜâøïØÏŠÌ¶ X©Ò5*Ú7M»/tD>–ºŽeîð5Y)BoÝ퓚§]G´ª&BrEÖ ™Ð2:Ó¸;W/ü",#µ‚ãåÈ üâY ‹ƒÃvöÌÇçzÞÀ71²âͪšêw” î>©ŽÔ{%ê}W¼tR¾ÜÆþ¤Dƒ„ÑãÕá9—QBààÖÌnéX Ñqõ€cKŒRŠÓ"p<ÄY¼ Àí’ÈÁŒä˜K¸*0NpŒÛ1d>düá;ÒWüÉ+~ÏÉ;•»ç¤ïä^»ÄãyÑ«$ª8â‡ä2*¢(k‘pÏÂ[F$,#ÕèÍX΢0þî-¦«:×W§»)ðØ%Ót$Ž<Û}ãqý’~ àS”?ê˜ñ²aôIØ*r¸8I…\ݼ+ž‚´*jÎe”˜«ªñ¤OüÅÆ˜-¦úÀÕ°HŽ‹þ¤ÒšåáAãÐEš¾³s‡ÔØñXÑqþ†&¾ï{cN›#êHZ%²a^'ìÐ „_h…[¬6•Še$øâŘË\ iv\ÌKŸór§B•ãK°Áa–Ÿ!’¸xûàœÎ©>ñAÓKüJ\ñá‡v³-ô¹o•"øQpQ«*ô! Ü4/ñ2ºÀ_3¾èe´‡¿ŒHRj]Q…’ÏÇõ“pe§×q1/EÊ8~¸+Òr„ ŽKC`{ü$‰‹×§tNÙc\ñE‰ƒóNX}2áÍ08ôŸIà(ŠrOù|q,£3s™#òË\ iw\ÌKñÀÁÅ8ج*{$«ê>5øÖ àXÔS.>hT¬+^Ž Â+^O¸Nž9é’îLäó™Cûø#£)¸ÅjS”ôþ£“Y±Àc‰"çòË\ ‹­kIY4—OI6Ç]¢Ë;3W{Ü;¾LÞâ…}Ç<ÀAH^ñÅøj?sÑwÑ'{Åão¿àóÄIŸd7Ó©6Cp‹Õ¦x§x7ñ±Š¹Œ|ì2šÃâ_F°cL»ãb^:Ë˪Ç #-GÎÄÚõ+°8ÒzÆy_+Ž\±|í¾ }ÅûX{‚"Š¢Àá;I¹ŽŒGQ§ô2’6\q'ÿ >Ü_ ªà\”‰ep(wÿ9/ŠÎªbBKÜáÂq½›¼Å{ NhâÁqi‹CêŠïF5sýÑQŽ“zBöYû™ŽØËˆ |‡ÁáòÉ.£¸-p5¨Ò☎€ãï"“c19Ê"à髾kWÓª F6•`p¼8tÅ3KŸ  j\´ÔÉ Rà€GfÅ8ÄñŠhõ(:™bnpP`q¨ô—§ø1ޝ°Áñ%%W•D{\ö‘ÿšÄÕ YysˆôÇÌ£ôÉWnI¤Ãtàƒq£AÚÇYUÚϪru$²ŒBÅá»)2œ»›hV¸Tÿ¼ýÃ~•sUéjOóûªËtV¿—Äå u@±íñ˜W|nbGxP‚^´5lèä=@82 ¹‰®s[>çK´ŒpÙ/ò$)¹°‘¤ªSLÕé3gÏžýøã÷îmŠ|EÐåð¿ñÁñoÉ\¿÷ ó€œdzðú8®xœþÂ9§%c¾_.AÔó9•ã 8—ÊÀ(‡Ê\Ñc<øÿÿBÌe½†Òí¸˜þù”H§Oó,9ÚãîØñ+˜ ›Nñš Q1ºÓé¹+›‘t.«Jà§r < >ßþ$'˜Ý¦ýeDú$+‡—ç,£3L0Ì'1ÆC誊¹ŒÀÕfÇÅ]U1ô#™.‡;>‚«.´5=)îùØÅb%HÀ¿UÇábïlö~tw\Ø%hSâeÔsuî!"ËÈÇY ¢:ŽNîÿtFwÇ¥¢»ãB“ÃÅs\Ì?”ǸêÒ)þ …Èžbñ.[k£ïd¡.ùõ §&q5_>.ÎYÌ{˜Ç¡Q”\F»¥–Z5’ËH˜Ð}²™E'O6„›ŸEt¸úuWŽ“¡HG”Å!Ý«jÇŽ¿…C˜NñG·Kù¸*,nŽNn$«Šuk“Âô{þæ$bƒ„·ŠŸÀצázM¢Cª;nh…ƒãÅÜd?ëEA‚EÌe”¤Ž ôJŽwÞyç­¯¼õÃÝgÏŠÀñŽL:îÎÃL§øÃ¢­1®ø‹\/\^wÜ"„ë_Çàà%ÜpÀµ© þ2ò…²é¢ ׎P/\Þ2*6S¡etQjq(á/#˜9® pœŽªÇ%€_9ËŸ~'ÚÚ`ï„#˜Vñ/È‹ý ù}æ|‰ë$Ù¸™µŒ¨¹–‰Oâ|°Œ2Øâøñ.\:þW:¾úã˜GHwà¦S¹Þé…€B¬=T• rÅXF±Ô9es$5¡>%˜Çñ®YÕƒK–˜›âÇm8„éÔ^~·ˆ9÷Š sÃÇÓ^8ܽW"È‘LÁ2Òœ>ç÷ªŠLrÂíq§NÍÙåpǯà¦Sƒ„DíVòäã×f B6nF,#_ò—?x6‡[càøá®]üKì§%róª~ ‡0­Òñ ɾâÀ7yËè¤äR¸ËÄ3RyàØÅ ŽãnU…;Ep1dÎ2òÁ2Åý;‘«Š›9þ ŽS§£ÚãîLåDÐ {’š ãCÆè?<¾Ç—*»–‘Vt9ÈÀ/«J·ä²l_õPÈ¡YSµWìa‡Æeæ/£‹©ZFS¥ñâ!WUhè¸î¬tàN(äP’ÍÈ/ÞJjõß5 e[Z—p]ðÁ2Å/å–±8ج*lpètE:åþ0ÜJ,(äH³îñ Ôɤåãò“ï j´Æ—‘àtŸI0%÷b\5Žmh–×ÉpÇ®H¶8ˆ³ñôybšÅÙԢqbfXFóZFzF-Ò½¼¹²¢²e$¸(ǘ—Tu6\ÆÁÅ8ŒRàˆê{`NYz5)òÕ¤+¾H°S„H–ÖE/Â2‚Žê©¼ lâÔÜ6›úÕIª:}–—ËÆ8>cç|JÇ鱨f,â^q¾Î*qH ŽŒ^Fú“‰ø£dí Ad–Q*Õ¼)"lx¤V—ùleUQ|pÈ6«ú¡,1‚ñ+Oädßwß-8«)Õ=ÁMÞêKº‡AXF‰¯!ôÇ ²ÅR%èOj N ]U¼ú?!‡Ì @™Òñ™Ñ¾’œûî»ïþûÑ»+P%Q‡‰dgV rïaØ8,£y¨S¼Œ ¥j±ÀÁÁ£²5e†oÐßOÃÙ¸lCu–ßW]ÒO…¤ED˜ú´3ƒAzv¶ÓmÍ›š››[ÚÚ†GF`’ ÑÖ$û§‹ ¨áÈ@Á2Òˆ«Š‡6D\UKÄà[§OËgU…JÇéb†¿ª$û>NÙ9Ë(y¬Ê/¸É‰^`%BÈðDͤAÁJ.ô’ß#øq0A!3—ËHMšÜ$§TD‘éU™½ŒvÃ2R§á‘ÚˆÇ,8÷-^CÝ^ž=òã];̈|öv¶=ç¾k*J‚ݼ)~U¶<âÕ*Ñ¥êšÇ<ÀÎ3z°ŒøËÈË ØàØNÇeBßâø±lVÕÛÏ%¿àÞÓqQE+ø­â’Ø9`M<ÐQ$úD.`–QÂæFÔ2‚ñMi3<$m×xðÁñw‚l\dqñãÀœàøY”‡lvrdd¸­­¥¹2NË£mî`sI 'ˆâÄv‹'£~D4a¡etq¡Ë(‡5M†Gkj ÞÄñ“¡tÜHpœŽwvÈÿÑÜG¬_azbx¸u.€T0s0lÞ:Ë@J«åín8y2ä>ÙÐPì²Ê}¯Lâ}ŸÝn³À1V»üÁÉîQD®kÏB—(mš–%WÌßâxK4rœâ[gå]UžˆÍ~¶ Íш #+6U¶;$t˜ˆS:ù/Žu»¡úá «Zý¢³í^Ð2"\F ôØrȘ8hy‹gU¹£Á±s…òé‚_ópkEE…tW+•GiȺÀëÝ*ÙXh<â(„z5åŸo›äÍaáËÒ©¢áá–¹ª®sUýŽ_â)gÒq))pHeUý$¦Åñ@Nö•d¼v9ÃcSËÈ,¬ á%/ﮊKÒþ…»6þ­f ³J5n‹:á™o]à2‚0¤" ¶¸Ê¬Ø€Ó£c¿“rUq1B®=®”ű³ªD‹ÿ{౜œûï»/ë‰$ƒéáéÝèéÚüw‹ÖÁxüTdu® 2ú”»å¾yp!˸¡hÈT6ˆ5'3èÑ>wö}%ƒ"‹ã­ps\\Çaç}õÔùèxTƒCL’eÈÔອç$óPHÖÐW´ŒÐ°N:¨›×õN핆"vp€»J… ”HtÙM@ xxžËè ë4»Fè X7f‚ô8†žÖg9ÌqˆGŽ Á±Sµbp`hdsC:rJý÷’ŒÑ– ‰Hy3$芎Ó*jî…é 8¢<à®RÆìR'Ýë½7µ "iÕD‚dçæ‚FYx*xöRÞŒ¿3áæ¸\Wu‚°óÚªŸúk9pìܵÇr–Þ q?SvÉ•©äßÎéତϪåÂã4¹ŠLðzÕ Ìýýà®R—ü©ÓNα˜Ht²êK8Ôi»è9§ÌÞ©æþî{8¦sŠŽõ=†Æ}9nj˜÷Þƒ#6M·TÄ„ž7‚6ØÃñÚ·®D,ûsx¬êq|·?|6B†]‚à8¶8l|‹C;À 4îgÝS÷#Kc<õ®˜É6 —Uó0¬Á¤É×õîjšëGõIý7pW©F³nÉo‹ïJ=áŠ'ØA¹NÀN›BQ p4·2È`'7úJB‡ûDSÁKâðÞ¦“ 8v bhwb;ÃIþ–78vþäíÒœ¥ÙÌÈq ŽœÒ[‹w¨Z¤Ð9Vš\ú˜Î½«)ËaJÒÏAõÑ¡ Idá2 UqÚfs,£@VCšvˆ#­±š¶ ðÏÌp|qqú¾ûX[ gÙcÑSÁKJqþì᦯þ_‡gO šUEÏÿÉÛ%ËrBLº?+§êÓ©ÅÞGI¸¬šÛ`ùŠ4¸Wæª×»öÆù3Æ$éCAv•tMzëÐ?e8# }nÜË” jH—jàªéŠ–aqŸÉ¸ýTØÒ˜sâÒã­væ=ô ™mŽKèÎò‚ïðýSèÏßÞW’ ™d»«>KÓíZÂ쨫#Z“M‡W¹¬f=;$Aou­Ú{-¡‰î~é&¸«”g‘Ì•ê¨>·î0ËHϱì2:Ü#ÖÒxn‡[dMJɱÛÃñæS=óØã%Ç%ôm¥¹ólØ£Iž=%›’»sçOþf†ÓÈ.黕Ö=¾D´£²s¥ÙÂþû˜´»ƒ„ì*ekJ2 —°OÁ¡Q½­! ¯l–æ9Ÿs$='5xôx¼ê{“¸=îÎ;ßþ›ÒHißý÷å”,bH#!XÉW LÚèw•’o.ÒY¸D Ô̪\#m2ªÊæá¹mâpõÏ_¥›™Ô«wþzÇÿf_INŽ-”p{ßÒªíY¢=V`u$_}¸«T¦~i3ñU»&庆Ç’:œH½øã UÕáÇkpç)œ8ÅüqWùï*íhJx¬†aW•dɹ«à>¤LÝ[P.H™’.cÃ0h‰yÇN¨^|Áà(y,ÇÎ4˜zpwÿh€V¢s‚žŽ²Ý*¡$0É’sW¹Á]¥@-0 ¤ÄÛœ5*˜tÒ‰¹Ü2‘ï£âmaÐX¶”-í㚈Œ”}P7‰&w4:’,pW©çT‘ÉÈÂ)H#­Ò%áq8æ‡ëk8opT-{`i6盺‰ƒ«Àñ3eu´@«ä ²«T²5Mb.H š–‡7ÇsK”óÇ#CƒKžÊʺ?物µÝÙá ±ã¬’*éîÜQvŽr”†,Ü@ŸÛhw—B`1Û€ L`£%Î$ á„ço$ì⺲U˪>UÛý`V æLkº~Ín³Ù¯¥ð5ƒ»JñòK÷ ( ¤nY„¦ ÛFáð'ÝØÎ½mŽ·ú2þº¿yã±eÂÒ¾quÞq£Ñ1‘A· .qߘBŸ¸«.é,\*•Ùo£‘‚ Gr5Ò,Õ¼°9I|{#Þ°oÜÐ`nCU¥L•†Zwê4-NÎmÍ”PÇxØ R˜t Å€JV:²p¼BC¬‚ä):YgU¶%’ö“¸Ÿ*np”, ÷]_VuóNPõ%³mBHWdFóÃ)ž-`HepG&eÜU Ø;HÛƒö”Æú®AâV*vÀÃRmo+[GÚÓÏ‹Á'=^õDLU=Àx§Ê®|ª™ÝÂt‹(e-RsKã' ɸ«`2`º•œ^¸‰J`ä @žœÍoÅÂánT$˜O%@W&øÉæLóWÝÜ4Ü)}.È®Rä5MY¸† ‚æÒˆÔˆìæÄGdlZ72U‚¶ŠŠJ8Z5¤øé »Jq’ÍÂMõÎQhºáL,ÐØ¬Ùh›‡ÓdùT hk¯YÓFG@¸ï³¥úùÆ à®R”d{á¦ü™…ñx j§¢i)UóðìGÕsž„ùnŠ‘—¥;8)—]j4½Y¸< A)GâØh•ðQ%ÃVS¬Kžï©zf­š ÃæMZL̽#r]KÃïîªÅWº³pù"Œºgg.l´TTT¤f\i¥RC¹kùÇ5E èa-º«D-liñ0ƒ»j±×ršzáÆµá¬Ï…¨¹~-IË3jVjÝŸêÊmF:41áiŠRDýô®Zܳž®^¸2û†´¦g¨LÓmQØh›NÞyc‚ã›æ·-NåòÙ ‡Úfz·i­¤C”X£OÛÜU‹gn¤¯®Œìiª@UŸFš£ûQ%÷64Û\Y1Ï;ÛìÐûk7=‘šWþ?ÆñÌuUŸ8õ»«Å4|€ÞUiÚ,¤) W°kþ*¥pŽäî>•›RÚH†&8oÒ†”˜ÇÇ êÎÝl‹–²«D÷´Æ%npW-‚dÜT‹Ÿ…Ëÿ(h;ߦµ"©Å~ÉåÆºðm=júÄ•æã†5¬wmSÔVÜU©×¸b²pù·"Àhl´Dõ1T”ÃæE¯×§àü]çsã™ j<…Íü³§æ¼Ük"oEº{ZCvUÊÏ8¥œ,\žüÐvd.kC¯T6‚ôPŠ=I‹c:·-é]eSZédW¥Vr½pÓ~ÛÆÚ(8ݱ±‘ÄüÛdIPÙý\ –ˆ*ݯ ÜUmjí]•¶~ê1$ã®2€»* záÊHTÊ1'+¢èžT­Ês ïëϦàÖ žaHç’”t¨µë¡]‰½ »*eû¥eáò4 mGdÝ\¤E‰»íIÁm}] žá9Á3SëùdW©3DžÎ~ê1½«R#é,\êš2~;L•¶66 ›‹T´(ÓI3"¸­§"výaÊmšE2:øîªÊ ¾·R v!»*ùšr+/ 7Ö`‚,sÏ„­hUêUÐõ|Íû)x†!aCŧ•_ ¨Âù¡ØvÖ]•lÉeá*¦¡à´‰ÞšVб¡Üð±”;’¦ϰVÍgVÐ0Wuµ€iï§CrîªäfWñœûô]ñ#š’t/\]•‚î’"‹h<˜éb£¢bS‹’3‰6¤¾°{V,$~àªE]w;Dúû©ÇPÊÜU¤‘$F‚0m6s–No_jÏFYìv“‰D÷W’t8´¶uUT/\¹mG„îŒ „Ä#KlMê{Kuø}1Åïzجªä*EôS¡TeWpP,8ÌzƒÝ²Ôb³=l±/5YI’¢´Ù^¸ŠÊ6…TËhlûp#„´(<ýF_“’•5©îfUbƒ’"WQUÊ—:¥G#“æ®Zö´ÃQ¸Y0”N—·|yžÓi_þµ|/§r¯§ºººÀmyØbAß ÃH¡ƒGŽ˜L¹ ùêHRsÃÙвU”IÕ¬ønªÂØøúÔ< ÏäX«~§2sЬžäª*‘ÛB‰¹®IrW=ý§ÇêBì†Bà°ç-w.·,ýÚ×ʽïÕy½ÕˆÕ+ ,Èò@ÜÐ”Ñ ÙèÐ8”× Wp¢ýA¦bc¶-j¸Ÿòé£át8²vB'š"¯PK£õ¨~êŠ$øÂ²«^ØèȵZm6’°Ù V 7O¹Çƒ@QWW^P^ãñî*‡Ãh4êÌf§³¦º  Àë©EÜp{<ÈÖ@oOv6AaXAY,%%OÇ[î­©+/ÿ{,?wî¨]ú•Ù WŽrßvdRÔ:½B-mðµSc±¸VYAÇ€èVrG¹¿jâî*šFà°Iƒ>KçtVW#$xjj06< 9<å¥Ä ã1Æñz÷!p”[²08ù­öÍ8)Ý W©Å Ÿ ;Û&ž%®Œ°üï¹ (n³RUä°©(™pï*ÇS& €cîùž§§ ß™•…·ß:d…X¶ØÌsÖòå[í­óîChñz=uÞd›,Ï#É,ñ»»ëëŸQy®ø¬A©½pe”ÑmGèa¡—ª¢EEŽ|µÏuM§•É#G«ÒsÅ”ØO=†Ì®r8Œ 8p¼"ßéñxòWde!ょÙb·˜qÉ_uuά*÷`s£Úë)°/'Huì*ŽgÔ½$ûÛ WFS îeòû‡(¸Ñ¬ª¼!!8&`€câ'W)½ˆÜ®2o²Œ»J&×ßh" »Óbq:³,8cj©s¹åá<çR»Ýé^žÇ8¬/¶8ª÷!{Y'yY:„LÊd;ÎeݸWÝ+Ò­Ü^¸ñýÆ£™rï §ÛT˜JÅ“ Ʊ¸1_rT(› í§C2“Éqip¤ÝnyÁ#ƒÃ‰ ’÷Ó¹ù¼ G:05jj¼ÕˆØÒð:yy:É‚Cg2]<—›»±tf†ù3ê\udáò4¡mG)¸›*U7nv-xª°oà—*™nv"•vW¤öÇŠìxªA8Àà¨A*÷zj/jÊ êê°•=S5è‘ùd•dg 2¸<+³ùP=Iæä8r–9–©³ I8ÜJ¿Ñ”ò«R“/Q ne« çÂñ†e¬ƒ¹9 ¯û^—åž}÷Sõ[Kº«¤r«Ö‚^dOðÁ@ ý5žrÌ lq00ñä¯@V‰-;›±5°§Š"ÑÍËbnl$H†ˆ j\ŽnBuÁæ’Œk;BO‹Rp›UYÜFÕTQ÷â“#RÏ¡\›£L1HIw•tx† pµßcœlKQv n*‚Pß{0QHj,ökŠ`™Á™©ÓÙ Ç®R„Ýî)ÏÉwX,:Ùf2Ñ*óÝö+¸®Œ2n‚,=\¡êàFä…paŽg§ƒ y¨UñÍré)‘Óg@5WÂ]%ÝD•"<¸ÞÏÍ|?õðRLŒ}ûjÊ1@<^OmMM©「ˆaƒDà0Ž#(»Y$ƒÕdRÛ´Ž{6A®*~{‘{mVÛw zB”KÕ¦â¸òìЇGÁܘïRhS<9J Õö¯ŽvWI›KF£N—ï^‘O’ÇÛs]YYùùÈqçÙq-6/ ‚"³²È/¸˜ ®`žýžo·¶ýg› ·>TŒžBšû»x€%ê¸#Ñý™ÔvDœKÕ»õ Vd1T*’÷(_›QÅ€nyp¸18Žcp¸ÝˆùK-YYL¶-…Í üƒõU±È ÂAVÇŠ‚ýoxê¼ 8 T Ž ŸÛÀSª±*Ç2h‚,?—ªÝ.F‚ L¯ýH‹ÉÑO¨:sEä®’øŒ]aП;ÞÓÓÞδ!ÉP0ƒ`½RDøA÷!²HH½>¯°×yÔyßÃ1õ¼<Š´*§ÉúSa}ó›s.»Íf¿¦"HÆ´™Îjª†ê ‡‚+£ú©«íè ÜU6éÞÛŽŠz}ã9ŽK TĘ%P‘$ßKzœ" ½±0Ï[Ç€£Î[^àÉ+¤“JÁ¡:eÊÙa¡—ª¼T š·(Z~aRSª;¼¼ì*R¦ Íd–ƒËÕÓÓÕÕÝUTÄ _DT@ƒ¼B§³ àï˽Þòêroµgér£QqNªo†¥½‹çnF´5iž ‚@üíD…ÒÈaÐÀŽ®Ÿš#ÃToÀ`ÁÑÕUô(Ï7ÅÏ¡ Å4ˆÈGyyN§§À‹ÀQP‹-K‹« h;B gü— &G…B3쮩Þà`®¼캗ë/uSL„™Híí«VGÌ =x)Ua_šï.XˆQ‹ÝTžÒ|wV¶ÞtŸIQýFžú}!8¾óÍ]:~õ4eL޹¹T )›CY]Öm1ØÙí&(ŽsÝ 8.µ—­âÂâqèð‰4ÙsŒF܏РŒ[ YY¸ýár§ÇS[ãñà&ˆÕÕˆn;3RÖfµŠÚn,SFÄØÐ&8ÄîÔ)í¼²ac*Šƒ¤é[¥”¢P·fërq8Œl?8óqÜÂåjÂjýøÄá#v;E± Tf³ƒÃìtbh ¿å\û\ÏR;®49¬VáÏ/[fJnUœàPûíH«d§[…AñIà(¶ÍQQ©›TýÔãвeÝâ\îñü-n.Uë)ÉËËwfY–.Åsÿ¬»}¹ÝbGLYj_º|¹Ó‰§7aKÃSއr8W ‡œv;A`‡–Õñ̺öö¼Ba­8îo¥p|ó›±ÁQÏHågsJ›Ûat£b8I‹7Ÿ£R 9weZ-®zúiaáárnŸî­ó”ãz Kv¶ËjEÜXj±,·Øóì–åè+¸½zÍ~úîRÄ'–ÛnÁÕæe³"p\*,4‰ÀAJ­pˆ cMdpˆ¢ƒ›4M Ò\ŠA³’FŠvrÄ5äoÛht:kjª÷yëêpÕÞ¾¿/¯®®)÷âO«™7ôQA5.þžpÂþ©ro¹ça»%{©ÇF¬v¿¿ïJß•²²ƒ{{»»ÚÛ ³²ÙŸÿŸ‘äÁƒ•­R48êyR÷ Õ`Û‘ÉJÁ?07’¥!vhíCÇls|m«…*¢~ê6­¸X8L˵ll„„Àÿy²šëõÖÖUãY²ÕèŸ&ŽˆØ—#kB—kð÷Uõõ•¹6ôöâ¢Á•÷g±?ÿO_ ‰ƒM—¥»ê%SÀÐ ûpª~¡ÒBs£rp“¥c¡Áƒë5– ÆëÙÃŒmnnMùÕ!ङ #M;–‘¤AO’:E¸ËöíGF¹Ùuï•×a ÃëÉÏ÷Ö!3Ãûdm–% Oô3ã¼*\ÇAQ££ƒƒSS úúÄàèé9Ñå²/Å?ßhlü¸§§¾³8ÒWJ€ã¢ñRã¹FÕ;«ÜÚŠÅM·T@É_Jt=2ê|í´¦r hž:.Í©özŠû©ßÕÎQþºÃ@FŠ¢ÜnoM&GAuuy]]·¶Æ[êÍÏGy¼ž'k͈º8êÓÑAÿÔÔL Hcpl<ŒÁÉ‘ëʶ³àh?ŽÀQ¿ºLáà8ÇIõàðkª¿ÁßMUæFµ>Ž5ÏjìµMD͈4‡ÙA.)nM¢ú~êsÊdr˜L&{ŽYdq½ ¹8_ŠB¸°b Þc³Ù6›Nw¾§§ËjEšž¦éþ~½¾ñnU‚ÿZ­e25Öw÷tw¬ßÈXGÚ^ ¼£êœHj?•j;B ’paZS25¸†¯c{u‘¶U2I¹ì†¤¢yQ jJsËqÃa4ÙíØš`‘Åô!HŠ t:«;³ìv›ÍÉ€£‘Ãaº51~ ƒc’ö÷tõ¸ÅG·ÃJ&[ã!üÙwë÷oÄ÷e‚£Q[à(ÕÊYZØÑ°Ìdê„k®kìåµU„\œ’î(:4l17qÚœ3CãšÒX\ìp¹\ß(*zÔá(råæb“#×U¼îòe“-§0ÒSSAúþ¬ßsôô<⪭e» ƒ}Wª½w¸êA°è½üˆ«±±«ëBWwWCýÊÕÁ I¦±PÞQÅ7BﺻÕ~"Ç´ÒvD0¯©’©’«…àxfBc¯¯5fRîlÈIéï0 ÊŽ£É•„¯#LjÀáú½"—ëÑGÑ»GÅà ‰ÜÜââË—ÖBGð.2¸²²~ÏÕÓSô(„ŽëCt°¯".µ÷ôôtv_èînï-ra{¡£»¾¾°×s¤±õHlpDÔ«zpˆ›ªM©s1Î Ìf¨ÝH²Ž Á±f­ÆŽ0Y?ÉSÓ¡u•Òß!3ú©q(»øÑÞÞ®ntógÆ8uujDT0žoïí=XO££4sQOOo,³ZѽѨg;å–•Ý \©#¨CM—ÑÿïíýnýÊU%¥7¢ÿÙÝÝXo³â¶%æ¬EpV}Ñ\àÂF7#ÕŸEqÛ5fÌÐ#ÍP*žJˆÀ±æYuqáí<¢—Ïäb€#ú©‡Áñh1›PËq£«¾ÆövŒ #HÓ 8Ê^°šÎ;Ԩ׳ñwY på E55õ^Æ~ªúC«Vn,{tU{#G}£Íf4R„ÎìX¾5¤*Ãj>à`]UÇ”šðÜT›š5֙Ц‡FÒ_:±ALŽ£»›ñ’rGÒŽ@ôSçdµžGÌ@7O 87ª±A§+.îB7,-ºÜ›Á1Ž@ ?z¼K¯×éØI·¦,K)Âju¸lVk¡Aåªß_Zj0´·wwïÝc$ŒÆúz’ܸž}¦^ÀY\Iýåÿž')xHƒ£‘gkhŒT?AvºEËI¸³ƒë˜D¦4¿¬!18֜КQ%ßµ*ô¥–T`Bû“œCàП?Ï€ƒMêîêi8„À±·§»§û Ç]Ž©Ù©`pænÿÆuFANݱg„ÞêrY‡Þt•‡ÞÐŽ,޽{) £˜$Wmdžhæ¿üœÿ«pœÓ 8TžËÁï1RQ9Ô–½1Úê¯Jï/r4ŠƒóV ËÈÚí§.RN¶ÕÊNg¨ÁÎŽíj¿ŒWÝ]÷^© ¦§gAzz:¸Ò·÷ n/B†GÈâ?F#þçÉZ\M ÇÆËVù»»uºÆöóÇ æ«Wé ‰Q’«ß{ïïE£CºÙˆ¶ÁÙÈ”ªÛŽ kz^Óôz¥Ä£ëÅàX«µÔªH»šVé5–Bph¶Ÿz48r¬Ön8Øp|ýÛ{p}ß•ÀÌô4=K3àèë[Ï€ƒ`Çr¥FÆú¨­º55ÌŒ­*ìêÂàh?~¾Ýj;v,GRµ pôj â ²ªZ²³‚#mš»ÊžUNTar­˜4v°#=Ö…ùôpÊ×]¢Õ~êQºß`Õ³QqL ]8¬ÍÝZ{¼zufúêU»ÝïŸ ÒþOqŒ£§‡³8Ø)OCöoÄÝ«¦Ñ†Ê]¢£ðÏÄ?æ»õøÛL&œY•ôÈø{ïÍ…Žï|':W£RñYAÑŸ[S ¶Ó¼Ã¿í¬Òš}ZN“RGêÀ!î§> Ù»M +Ç9n`##¼'ï]µñêÀ4G!.÷óß,.îîé9Ï1ƒ…ç¶Ú_`Zn"Žîúz–_OEJ.€ƒ/›Z; WThzðƤð>æ(GT1‡æLŽpú”(Ì*,O8D  š½×|ûÛYÙõõ¬}Ñú‹ÈÐÕÝÓ‰ëÀWíù¤éÚ',üŸÑAÿÀª•M‡Ê6ò ÎcEýý4²80:V—dWOooccãA’ÌÎ~êOSäp—F™€ õ¶i㻩4X¼1»Aaü÷ÅQÍòaÉ0G[ª×Ø=J+æÒŸ¾`Ë ƒÇ—X„\îݳ÷È‘±©Ï> Òþ•«5m\Op¼ Ãè ¨þ~\aÎÔ{”‘$¶]dÀñôÓtjÈààkL ôl‹Ö;¨UšgHL2íƒ×{d˜ƒ#u•¥U¢•€fï5ާI;§Ø㨺 “÷ôàÇ'W²—²l°Û陣Ñd2ëy*"ôo ÎEö=3è'Éöv÷*«©ø™gŠ©‹Iƒ#™qÛUÔ¬NWj½ÇÈ 8þ”±©uBphðŽšñ­)Ç]Í÷SíöèßwPd8ªÑή:M‡ÿý ‚‡Î¤×ëI*”Œ‚A”–â óéé=8ˆ–¬.3!p»R×pÀ! 'È Fo´Ñ<)QYLJƒ1!ø>Ôâa/¬æpÍe‹\Iy’$jûCNi÷Vã@¸ÐÕ})”ˆÁFo/¶8ÌÊ&¨PDclÌï×Ðç8ºApÈeæýý¥¥&“ÍÖ@k3› E®sÇ{Îçää8Rv?x“<„àf‚D.VäWDzŒThròF`ƒ"s˜†”e¥bK^Y-"p¤j¡Ñ¶ è§ÎÉäÐz¸$ª»*ŽîÞƒ«˜ÁNœ_jlÜï7PÁ/ÿ {¬8ÊJõz´Ù`6»\ÇÏ÷ä8rrRjq$:¨i©­íodSE³&o_Qè5Ï)ÃÙ0¨mƒ#ȯ¹¯R´Ò®i~€Ööö^‰®6$~©=7×f0˜í’¤ˆöóíçV#é÷“üŽ1˜ gïNOýrœqbQF#f~KqSõ÷Þ“‡G¡vìø¢¤¡³I‹:s–(û×ôPoÑ$ÓéAq“R™ÓCk5ZÅV³x`ŠÁ!î§®å]ªÉÔÞ~µ.ºñßöö\—Ùl3ØEçow9 ú-8’Ç ¾ÅQ˜˜ºó/ãø1ŠÀàèf<_VÅ€ƒÒ;viAZ4AVÑKwºYÓÅâŒ&¢Ší†ó»Í]‡@vL#Âé¶ææ6!”#yÍBp¤†Ý™ÓO=øío“äùóÝ=¸½Ho/Æ.×›š¢ƒS÷ú7–!ÙíFc˜†‰DÄSUZÚwu`?00Pâ&Iü³5¡ŸG9 F«©ÐêÀo…99GNÎl2oiï  ÎÚØµcÇ. ph ¥ê)\å‡Å+'4z…='æ†ÂÆ|OhÅy;ÛÌ÷ Ñ1"Ú—„j©éPœ9ýÔƒß^†Àq [x jl*ˆÁQ[Væö0à7¥¢(~®ÐiõD)âÆÉ€Ãí&‰žžâƒ‡êë)"'§ðZm.FFó>¸èà@Ö~Ó88TÓv„æ55¬ÔhxCÂàØ‚R¡P@£²mv6úÑM›˜Ièz.=öSg ò–!‹ãÜyfÎ+ïé}ô‘’²±;¸.þŽ@à³ÏV6™X6¸ÝUWV† ‚ÿá÷ýýccö‚¢¨ì¬’›…$Ï¥C¢ˆ¬¬e_ßøBäYSža‡ÀÞØ! -¡Ã¦’ ²üf¸Í³Z½E5÷€Y¸)R³äœúÈ$Yf…ò0RòdF?õf—aWÕ¹îîž.&”ÝÓÛþGÊÊÆnfqÇ© ˜ýÌ¿q¿ÉÀÍû+éë+, U‹“‘ÂqSwìvŠ YpØÊå0°àÈ^Y¸‘7w7MàÀæF&€£_%Hmüð†f¹Žëp‡O98í&Ã]ÐÚ‚ÁÙU’Š_ ú©›Œ&~[æØø ¶±!î-er ¸Ya0XRÐt 0>î÷—”àϦ¦Æ¦pCŠˆDÅqrnY)Âèņ¿F1¡ÆÆ³®\‚HëKýÏŒ0®víb]U±ð¡…“«Š¶#t‹¶›SÉãX´;¾ ) N§rrl™öû©›ð>‡Ãôíe¸Yá¹óÌd Vë(Ç ö*aTƒàøhw§¦+AX ¨pz.܈ó>é¡q›"˜8BG#"G®‹ Ž»3ØU…¹Ûa¥‰³+²˜Çø+òÓpµYõÒ m7¡UÒ^¤Y@ŽMáÉÃ=™=6vçÖÊeƒƒ==—zy„$éEýFÑØøƒ?@ï–r>2šµ<äÀñî»8»Êo;2Ûé†[9©éÛÙ¬ „CÛ¯UIV,%gää¶M¤"±Aƒ5t^ž{µI§³bpœc«Ä{zÛmlLØv;‡^oBàp˜ D£±¶lŒb7–Ûm³q}F:tº'÷#[d ƒƒ"³²ÆÇ§¦ ¯¢xþQ…€ã?GÃfG p¨ÿ|ߥžWÅÚT©õ`ñ1EVphS#"££’í‹IÃHåäX‘¡ß§Åâ?‚t¹zzzÛ{/ãÜn¿ÔÐ`s¸\VdcŒŽ2{sš™­#ãŸú) säèôW¯ŽÝÇuŸù…9…öBŒ˜²ÕÓÓh/³Ø ÓÓã#9…¿ìê:ØH,6þOblˆÁÁ¢AË6G™²}­ü²¿–Y­ßÌ" Ì×7R¾%®¢ƒ’‡M‘Ê‚C˜Íh¸«ÅãK.Gϥ˗npañúz’ÕŠÁAãÍÎÖ ƒ·Fý~Š¢Œ……cãÈ¢¸ úýˆî ޲éi|?žºå´çäLO ¹í¿ûuWWcÓ"E8GPëàð+y=É»¶[3áfÆÙë&‚ E@G…-hK;+ÂIJÀ!lÙP¥IpääPDc{WWÏ% ÜÔðRWoïå˽—7õ÷OOc 0Ø §§GG )Òíöûg%%Wúüþ±1lsàJœuU{„$ýŸÍ.ðãU}ÕŽ~~n®AŸòó<ñÁ‡ÔÿÓp”à`WŸm™q3›<úì±!(ü[pг­"t´M j†RµðÊ2 ½aŽƒ >nÄQ8px“ãÈa8–Á¸8ìnt÷)ñpôõ}曚e67‚Gž ÉÏüÀè¨ÓíÎÇ_é£tíí==®Ü78L84°UÄéål´©¨†û(šŽŠ’‹ãæ©G_FôS·šss;ñœØ¶•:bú··ûð¡þþ™ÀLƒcz:€>³Ù(f(,Mß»‡Þ3î+Æ"¡§¦ðç¸?•ÿæÔ]‡ƒ >ócœ\©Ã°³Rï¨ú‡(±ÿ@ÒÿW³GÀ¦Ôá•#•Q¾Jï2EÉ›E±T€ƒ?üÏvW«GÖjÍÍe›Œ„Àÿíímjêï§(°àƦì6’<|T¯7¥¿ýmÄâÀÓ’ØGÇ»HBlh ?Ÿ J¨•¯cJ›’ž8:še,0w"“Co:ÞÈÆ8Bèh<.–ÈÎ~rcII)RI‰AOýH££3l¨Á¦´«Ñ¸(/â·<þWèÑü@Œü@ŸwE‚¥‘Š«¸¡¶}åšhUgg±ÙV18À俾îÀã\MV£Ñ媯çêÇ»ÛÛ‹‹ËÊpõÆÐxÉ$i·×>ùD¿Þ@pÃþ*;{ÿþ’’#˜$ ¦Š|ÛGŽàoL8"&Öæà‹†µ‘ü»ÿ‡CZ+A޵êôWEÕ’'O=õÔïgàªXÉ €Íu $P$aÐÕ×#p\ÀÍ‹‹ jlÌíÖë÷oôøýÁàÝ»ö2<¶ Ïûc91\é Ò34;JǃA£a±nHè—ãìà|Á!DÇ]HªJZ*#íJÌêÐÑ5Rú@¥§O%OÂDàx*WEáJœBëpèõ:6+··wÝnЏ…Áaxrã ßïÇÝÕíöÐhqnró†ãå}}LW+¦ ›Ued&<-âKaÁA' ™qúEtDç8üšžI:Þ#S=+ ~C-ºþŒ$9žUk#|^-ùBGŽ?Ö7¿™që•K’fÃå/_¾|éÓªª“IÉím·Ûõz½©´t68ËT†ƒccEâÑ€¶'ØÁ°èôÁ€g`Í ?LFÕ˜û1Š¢wXl x÷ ùÁÄèˆ šË®¢ƒÝ ó^–ÑH’è/:‘6›Ù¬Ç3º–Ú,³Ýn2x°ÑáHú%›!cb5¶¹ø@kŽªö*×’/´UUFƒÃŠÀa3´]¾ÜÛÛ~þ|W'[ÏÑÕs¹×n×éô†’Rš w#pÜAÀ °ƒÊÈ9«0D0BüLEGw¶Â¹V%̬4¿´8ÀAKYZ=Õ4yÙ 8Ф(‹^o_j·ØlY»ÝfbO)ŽdtòçÕIŽ ’àX«^gî4kú6/,ÄÁ#¤ \¶l’p=ÚÞÈŽrêêî½Üs¾±qe¡Õj¤ÆÇî28À7Œ›d\ZfóW¿úÐù¹3Á8®ŒFìžšº‹s뎻 Ý‘ˆ´¿018ðçü¯¿ÃHtsÕl?˧—9…«ÑÙC§ïáåö¼åN»ýk^¤jôæõz¼ÕÕ[,›×päHòª7'3 îг6<;¨5{uZšƒj~ImÍ¡QäŽùÊá ÈGmÄà`š^îíé9ÞXè¶ZIjükG„¨Ûl„‘¤tó’¼ÿôÐC¹¹æ,6\ŽÀ1 ÞcÆ:a{Ç=”ŽS§ÞyçMÏj;NËyÞ–=ÀQˆë7uºåö¯.w.Gàøš·££¦†‡§`……E1àx>iàˆ$TUhuÞßÐ:­Î†”J­:–ÑvXÈEÒw¾“áé§&B÷hQcã¥lt“dnnÛe£Ñd¤iÜ©Š¿”–جöåo¿}àíHû÷å>„ÑAW¯b'˜ž.)Á±sE¼0>:~ðƒh‹ãÀO‘ÞýéO?úH»'÷…«Ÿ°ÙV¬ð¸=åˆÞÚºrOMM¹§Î[^»"? Éb!¹´ÐéÌÏÏwR”É”›«OÒ‰ä%â¶hô@¹»jÎ7ýl48>pd:8–-3™tº"ŽžœSÅãc£Ád½>„ÛªãÞ·%¥«ÝþöÛûìÛŸgÁŽr’¼ZÅÎ Dàð‹—ˆ»pÐïœú)'-ƒcãF«'p¬(ð”{ª½ûêê<ååÕ"ÞZo>Žl >qv»s)G>it˜\¹ÉÊpˆ$TµhÓ¾›åûsÖi¯²lh­–\UÉÇ7¿™áàÀ2™(B§{´øxcW÷ÁÃçÎõö62’µµ¸¹z«Ï Òƒƒ6+A=ôÐ>ds¼ûîYfMí»ŠË83ü„œ€#„±½ œ}7ŽŸ¾«MtØFÒ`0›W8«=ž‚êêÒdo ¡ÏÊ Ð¿ÙÙ$If1µ8T¶ÅS‚¿†]WÞ:Ç’e³ž;÷G’ò‹Dªšg5yùŒ¬Õún|V\БٕÿŽ8lØù]üL#G#ŽCM$ùd-¶6‚LϪzl*´:âá‡wÕ?ø‰Ù‚Ó7¯^Á& žÚA6U€#øSž´šv8HÒhЙN¢º¦ÆSÀ£Yžòìl gWãŒjÂbñ0ÂQ'ëÊ˳,[²À Œk´±á‡â*-¾È‰ âÁFƒ‹@–Þ ^¾ÌŽsjoÿäŠòû§§Ù9lûB:8zËï7u:yÿ¾o{ pûCB¿ò‰^·ÈÕñ¡#¤L‡Ãa2á`“Ûétæ» <΂‚wVÅ„ÇÍæ-‹Ùf¶,·{ ¼u8­ªšq\yÑ·-·“¤N÷á‡ÝÝõõÏ<³ðÍj³¶q'£"k5y OD쪵ڴ“Ž j€}6 Ž^và¥ö¦¦8B=¨p~ÕøèÀ‡Ùüöþ<àöà{žèôÉ€Á €l* £‘Ô‘d¾ÝîÌ/ð¸18p,iàS‡ó§Ì³9oyAA]wŸ!Ãë©óV#r,_NPݱd£EÛ‰¸C9G½kBSÞÏìþ€blDƒ£žQ’e+m6EPÅ{Ï÷œïéí9tP¯7ÇÆÆî°#bÙôÜ„+WvƒÞžc³×®ùý¸ ÃfSÜ ’ÃF0øÑGÇ ²7H»Ýbq:-–¬,‹ ýkøáåN§}©syã¨q`ƒÙ§3/o‰Žä˘¬çÎ[]¥{ú« k:¡Š–jæô¬fï'6¬Ùð~fÆçyàXYf³áv!ÅÅç‘z.<¨§ŒÆñ[8,`Ê5f}U.‡ÞdwXmFýµcÈ^p(E&#I n pØ080@ìy» Î<'{J`Ôì«ñÖíC•W/GàÐézë¹s¹¹{Ë…føuç‰:rÂŽf îÃ'þLª8îD”¹à@Äøn}ýžLCör,ËÁŒHòxc}½Õ궤³p``jŠ ”cÛWuøýWö³¾¾Â<ÜëPgPÝ ¡ã£´èª*XM OÓ‰ÁQS«7<^§¦Æã©Cª./÷ÖT×"‚ä#«äþ,&Ç*ÔÆ’" ³­ñIää8r–-Ë™g²\³–ã“ë$ÛqÀ ‡ dž™ޝ;rrØ®Tõ‡¬f»îÙ£Ú ¡Ë;ªÎ‰”Ñë`ÙJw!k{„n/ø^D¥%ª¿D´‹ /p„#ãÍš=Ƽ¶¸ êOÒS¥è~º²·óU+W¯^‰•«W­,\‰ øž«¿`±ñ4÷÷/Ãàø.F~C>øàƒ8zýºÿúõ럡¿#á6òùÈçŸþ?9ÍûÙA 4Êé& “ ƒ£ã|ïêUaj„-617ŒÝîv³àðÖáqNîü¬ûôúùMoÑré_ä>‰§T¬;6 ë-¶«J™ƒenÝ¿åñ[>?þ…·5˜¹Ñ/"Ç H‘2Y fRGRç÷ôôöbpô^^WL…C῎ièð §‚oµw¿×[^^îqÚÍfƒž hÌg718´¾¿7ÖOdÉ¡¼ Ì­›GŒÿûðßà^ÿz»Þ~qýú €¤p˜ÌVÜçöøy ŽžÎnŽ=ëtT$¶Á†ÁqV.úH§Ë²<ŒÁ§zkjkq‹uEÜTtüuãt‹Ö#ã i¬QO”CÎâšXÿ/Fˆ×15Ðßd?;´¸:ò<K’–å»Ýú1\Ö¦MH'>>|Ønç*7p@œ0gaá†ë·ºÔãñìC-›m'$i³YEÝdÊÊâwZMj¼„$«£ràPÞ˜9‹ã_0:¢V|ppÔ@p€T-úù#¶'ðt?Oׯ18Z›Nn²ÛF‚ëC†懗:ÙVë^dkx«=î‚l;þ)V«#ÆxMŽÖÁ1'&ÓÖáàZip|°8ÏŸ@ÑðÖ=ÁdA ÒŸ)YµªäK ÄÕ«W®^¹ê_è‰ÁcGŸ;ŠÁ1ò‹ëþŸþçßüó–ëáªÏY¶Þƒ[ñƒï¿>?:?@ºz–™ÇO8¯_ÿêŸ¿Ôæ+{ޝµkyäë|ý+ÿlíÊõ+ÿÿöÎ.¨­+Ûó`jÚã‹ÝW®I»kºæ)–ްÓ65oSÕ5Õ7¥¯c ROî­©~êÇ<ÌÃ<ôKLU¨ Á¦ŽuE¬¤£Ãì½ÏHBÛAÈÿŸ Hèè$öOk¯½Ö¶ð‹Él6ipä2ö5ÿ ³™e¶˜É½‚ƒj&œÁ@þ¬œÕÚÓc2ä†=Ú¸Œä’s•Úïx×?° éÀîréØòs½ÏH ³±–½·Æbõâ¡[`gÃëÎSäßë–Î9-Ò¸ÚI—æöì€)qüæWÿ™ÿÊíÿµ?_ÞÞ¸<cĹ;•Dê9_ â¨sF¼ƒƒƒÞ‘†Ì_Žà.»Í@ô@ÇÿN"G§ÃNL`æÌª2òâP¿°£Ì‹hÆB%C¾A¾k7™ Ìœ*›Ñ´qûD*wmâÈwÁr£W9ìÛ*ŽmVU êî&ö^_ ŽÅv8¤…ð¤ß]Ô†¯é…WE'³H|ñ¯¿wÓU¿"ÿÔª@-ž˜egÈšVoK Eê XAÀáÏBuo jxG;â°Û©8$€è$á 8x5†ƒx¢S;ų,3±• 1 sŠ…3©âà˜8ˆ9xbç%bs8·‹Å3ƒÁî´q¶ÅÁúüÓþÞ…C¦Ü“ú¤ïšX¸`‹Ä©êÇ–ŸÝ²ú·}o8Q!?µCq(r¾£HàÁƒ Ä$êÐ&«h†ƒhäõÎ W?ûS,™AÅñ_³<¥µÐU´ƒ¥I’2sÅ„PÉøåM‰hóVGýþénˆc°ó¸Çc5Ò˜FƒÑnv˜;ÙU »éW³™œ¤ºpð4ÈàØÑFKjˆ2x8ÂÅ;ìönrt;»­D*TƒÓÙÝmsšíZ–ÃS]¹ì>º±Ÿ½‘dC™ÝjÇŽ~Ïå5’à·IpTìT2‘ÝÆ¶J׬ÁøECwR‡#®x@µqêõ¬†Ãb9E¼qêÂ_i ¢æ½i§ZÃ!hWR_êôÜ_x’©"ÿ#³(c‚O ;éä Žúfap“ 9Іn)‰ˆ"ùˆD"Ñèê³gѧ©ÕèjôÙSò/Jÿ­JÏôä’(†„%1BþKÁ (ŵL*Mþ&èË_N¥Vc Æ“„ ‰½uQH’³«+Ѩú÷ZzïFvó°Ý ŸÏÀ‚Þ¶;®g—ÕáŠ×³ÖP—Y4tk `ɈGˆ6º:O= kq?'ï¼,lÞŠž%"ðûÝþßL±#Cœ|ãÝÑyÆÂ»y6ù$Gl3àˆAuÎb8†/?¾eèVÈ/{ˆ±‡¸D$É—È’‰¡ŒDæDA\ ÍI’(~ó”G!‰²œ]“3Ù5ú†êYT“Ib„zRl…ž]ŽEYґɤÅ!Ï,ã/éeã¬,€ª·®Ê׫¡À¢hè«óTzú›o.Ð|¹0•qÔ/óâð.4¾8ÈN‰ÐÁ~Iœ#îXBs4¸˜#& Š4$‘È9!D¼!Ryˆ*QR”µ,qG†ŠãijIŒ%Õˆƒ^ÆÄÁÎÇ–WÙ»%Al\q(ÊližÖ¿¤—Œ\yü¯šT¯ÜËj«!P,º¬À"¯›°€˜ã-TWó%äirœÛï׆…+'ß=¹)Žù¼8Î/ÐsJšÚ8ÒG½“)G.¬ª0t+9)—W%I$)#_b‰9ˆK!–H‹Úô“¬0ÑHtB*úŒ\™‹dÓJ.•bqÈæ ò!H+ÉÇO處U)šËÑé°†Gº\ù¿d²UÄQe®J‰U¹^ OZÑнY`¡4;~‘ŠƒÄDÍד°Z:<77ÐÑAÄñá·óóT #DÿxCÕHÁ$¹‚P˜2‡8êùÝãh¡9â6YUQQ%¬. Ä1ò‰~Ùd•$­.§“Ä=Jaâ¢O’ðŒ‰#I¥iÄYÊ'ÒòŠšóx[‰’kÒÀEiLq¤Ë÷Ë@~üeSeÊ©Úò¨Tå«ñ5ÜkáÐ­åÆ•Íøƒ®§¥ ÖO…tun´añƒ[}T DÄ×´€ca@‹8ç7Õ‘V %P° â¨goÈ…ÙñÆK³ÕMçbtõ“ÛŸS„€ÝhqŸkzò7º>Êi‰ˆW¯÷ôŒ;—#‘Ì9*ðy<¼'I¬$cÄ+AººÊa‘ˆcn)é§ëªÆ…XÀlwØlËI*‘XRäéQFQOŠC¨Ð,âxÙ„w7U•³>×ö…C·šÈ/ÿUÚ¾tîÑ£§:/°æ#ô#\¨ â8É<ñÇBoüŽ| ˆCMr, …™qˆ£®‘‡ ³qZq1•ö»ÝF£ÍÚ=þ–ß¾ÁÄÁ?´è[×JHH¥f}¬ÜÜewpÜä“•¤(C}œÁ`7”g©Ð\˜-Èí!±FØFÅf·´²ü-ÞåÆDE»÷55tièÑ Ùñ—>WU9;^µ ‰ßúeEC7+Þóå7ä’Ü™uû 2Ú4ûÑn1´÷…Tq\;ùÆÉŽ7N.¨š8¿±¬*rÌËä*jIúFCˆ£Þ) 9-=^,ŽIv6ìó¸Ýœñœ“8bE 9>¾ÔÝmuö™l6*Ž@,%ré÷¸úûÝî~—ƒ·ùEqI 2)¸b’(Lsg³šÂ¢ F<ï°ô-%H̱ºêà F›­/‹ª÷þë·¶ïn£§ú#Yi²c×Nš·î¦0]>F´OÖt§…Cw °’o-àß(Ô ûݽ¦r Gû±c]TÊ‘FÇÉ“WTq¨óT¿ciŽt¹BWé±Û¦Žı/ˆ7rz¼X~µ$ob‚ˆƒ£ŠèþH8ÆYÄaàl6bîpìY„E$@™˜èïwØm¶O„¥¥¥H‡' >1rœ•ã¢Sž·Û-½¥d˜†Ng0/ÇÁF‡ÿì@Ôˆæp—u€}»We®2®Ñô…C·ßW0“¤ÊŸ}D¸0àÖP©âè;FÅÑþUˆªƒÎ[Møüss D×þ;õÆ€ZÌÑqSG<®¤7¶ŸÍAûƒá¹ª«/‡¢ÕOøÿËU^ 9HСžv:ǯO^ÿý¸‹ºÜ‰7áp ¿Ÿçm¶ñ•dBŒjÈa˜™a­« W#©g²’ »].—#ºžN$Vziù¹IQL%sn£ÁøT ò~\©˜fEבæ ˜ò{Jg¬\Û¶Ë©yufÚq9Uû= Ýù±Ý¿,çbSn6=%ä}ÿèÑÀ{…IsEùªÝrìØ1“åO_æb·šC_ Ìÿ±C›£R‹96Ä!kÝóMª Ž}@#§Ç‹ÄÓâïä§¢xU•KÿM=ÝãgñÇ“Õe¿ÃÝïr±ÆlŠà"ñ„íÜD’®©êcæè³°ÎU}´]Qr‚ÇÍ»LB:•H„œ‘()ŠÉdî×DÆF‰6r•kÜðÆi¤0¸]Ì ®š2B8à„…åïi^0tÇ|Ðå¶¾ $ÜøþÑ_ßë¼ÐåÛXú2ô6ñÆ1bv·v8‹H•¨/ŸÛÐêÇó!ÇB<>µÑÎâØ'¥Ç¬z¼H‚*Ž0«ÍX«©îëO´Ü8;­N\­†¼Ëåä§¥T*›Uü½¼ÙÖ}=‘ âª¡ƒqÐwoi"}:I<§s]Ýá•9r™h´Ýn¥q~£éç™-/œl͡î_ƒC·ºVJMmÐ?·zƒð?ß?3ð¾o2O¡Pè«7iÄÑn0¹ ¼‘Kú}Ë«F‹BshíL¦ˆcßÀ6ähÌôx‘8Âìœ_É)´¨o\•­â»ÎN¯¨DImgÈû¢RJÎʹÉ^Þf»ô‘*ŽÈU.ï ÇR$4ÒSt–94]_ŽÑCÅ oÆi9n¶Ì»s áFS0tOªx¸¯œ¸ú½ÊÃ÷Þë̇ $Þ}ñÅWgÞ<ÖÞ®¶ÒõùÕ‹X#u¾gÕ»…kt‰8X‘ÈÄD,qì8=^$57>›‹ÆD1¶|Æ—®Ó¹©ºÙI-àÈ oØyž7L³r¿”¦9çD,ÊÄʇ– -$—3%¹\¶>1D³ìN§S˜£-H¾Vý½JâˆÖÿèûmÅñÅ™²¹•_hE ñk[2ãGÝS\=ÞHYŽÂ¡[Í“W~LŒDò¶XÙXTõñ†8&ìDF>¤Š#ëbâÐ^VÄŽ|Š£/ sêî$˜qõö®:»Ívãô*3ùvÿDõ™µ[­lc @7¯”8ÔºñpFG¦œ6JÅQéF‹£Åk£iô€¬@ûŠÂêñ†äÆG8/Aç7α„8Mk\ß\_¥ŠÃfRÅ@S‘°‹ï%â†èþO‘ÎÍEU#‘¥P*Š¢ØÇ;\î>ÇÙ¸ñä“DZ¯E*ZxÞ`âPba—µ7€ÔÆ«&%\Xž—yqdŠÂ VÓ16Vܤ âØ¥ÇSüV®_“‘þ ›©º4ÉÄqŽHäÒ Ö}„ž÷Ùè–±& 1ØÏó½×G ZÆÁ”¡ÎVYèåQ*Žˆà´Ý@Ìñ b¤¯EiŸMU S½|oXÆ(;tlÍG´±!Ž ¡¼6*«#NØ4ÇÀ»cc´ö\ÈAû¢æêÞõ†‡OÇ2‰8I5Ê`Ç“žºÁøÃˆ$Ñ]e“‰ÉslgòºýFˆ®¯bghe†æ ­n|š5_—•Œy¨!ÝÝç®?&·( [Y»wÔ2¼zâ7sã9*‹ q|¿kqÌçÅÑ1Ö1æžrÇþ 9¼Y>^0t§]ZÇ!ùn"_7NƒŒî+Çqm'yœüÎi²8ƒyZ M[ ¼‹ö–‚¢Èj8ŒD1W¬CŒJ aMŽÌqš8ÆWhaˆ(æïÝ£.ÇUê¶íH,ì·£A:ØN1ÿfnüû qƵ‹#—Ç¿/hAÇùóccc³©ı)ê‘;Ø0« Ä¡6q{V£ÎMõtwÄ:Œ|gµŒ7ÛÞéY4*E¥>C!Ä }lÒjÚ¨ÚÒ*n:ú4•¢»FB~Þd7»»§V’«´üO¢ÛÉ­œÉz¸ýhûÑ£íä£î~;ÙRi`3?PÃнpÐ;‡¢ÆqFí÷ꉢ-dGPaºÄÉðÛi48‰9zÔ‰ªDâ†ÍF;Þ2q0¤ˆ©ÔbPXÒ¦£®FB‚¨sX¢ÑTj-E΋a‡Ùl0œbÉd2éžæ"m kmo?B½QâHì»ß§¼Òâ`¡F¦pš*—ûú/¥Úøó_ªÌq”qìS‹Ì±Úxâ˜"цÛŽ9ìd€§!ǸºexâºÍf7»Âä}¢Î*eÒB/§F³ÙÑ‚¢ša³Q¦>šø².¹‘òÝbm„–4q(Ù,]Ä»ÔGðr}Éd2!ødëDY­ü`ZöŠÞÀTØ^[yë­Ru¼õ–›ëŒñçù‰mŽÁ“Pç|;ØxKr7†n)`²9ާÓHbäñã|•x2)Šs!®Å•$QD ‰~й$‰Á æŠˆ(,1cÐ3ßD"ÏžfµÈ‹ˆC*˜¨ %I¼ÁĽʙL.Þã1Ù„ôr‹ÂênÅói} z'a!¶ÓfPùXe=&$·Ý#C¨è ;šP!zží±¡]~ÎyÞy¾£äJîíoOB½SÔxÄ;ÔõãC·`â¬=Ý=ߥ›¬ÄH˜!ÄÙµâ±7#¯e#¡9zq(•” k$ìH¥ŸJ*¢È¶Œ¥s[rŸÉnò¸z{Mõl"‹…„­÷^³[†p§Ë«íºrŒ+S_/¯ÎAÙ·©ã‹U Á®ø-á?@Ï^þá‡þó‡Vx·ö3ÅU€£õô¤Ê»{0CwÐÀqÝÝÝˉ¼9žA>)J‚$‘C–‹_ÆÙL*C·i¢ŠH¥Ö²k™<€Ì³F”|—|bHQd‡ÉÈÄÖ×éÔ×\hw⨴…w€àe‰ãŸ!ŽF¥¸ Ð['GÖµõ^Ã;O»læ8¤ÈÒãä“'¥ÚH'’«©§ª8Ô€C‘×I¢»‰q(L$質4Ô£¢Q!”M)J6E+Íé÷ž¦æDÆH‚°;q¤+g««OW%»štÊò¥Òð‡cø Ï)޼:Jµ‘Ëýaô€:¥hIn¬ÉÍ{cÐ;8´Óò’‚¡[)?IUð…F)EkC¨Õޝ‰¢"q›‹"&ÉmNdÉlÙn*S…†îÈA¯•JE–’OŠ&ªv&a·ËœÜ¯×[ûÔ˜'Œ)*ð’Åñˆ£a)Þ˜£.zV)E‹½vuŠC\’Xõ'ÅÉd4¥ê@sƒ"ËTÑg웡ˆ*v2•CÐŽTT ä;Ï–ÄÇ—“s‘]Š#\YöjóG©Ê׳V¿Çd€-Ჺ'! ð"Õ‘âxU'«êaMîzñ#òŽîd Ýl#¾~ºÑ’ÑÚÓ3Þ=Þ£6U鍊9õØœ&ƒ«×år»=äƒ~u¹fÚoÄî°ó&³ÃÌóÞb7Ù\.½ãà \o/m2bwÐkð½FƒÝÞí´rî-Ô¨Ç*GÕüød•ëm·°VžH@ày‘‡‡ê,ÍQÜ e‡µ‰EC8mVÛ“—ÆN«ŠÃ£Šƒö³r»h#DƒÁav8L6‡‰ïuØí³‰*‚:ÈÅv!§² ÇØéw{y“Áa;×ÍíZ¹¬uwIŽÀsˆ€—§ŽÒøÍ›G‹#>TœæØss,n‡·öTGÑîšàNçF´Á¶w:©¨úɇ‹èÃåæÔý6è?# F<®‰þ~m(b—&‹ƒK›µ{xm‡h4 »G•\EÕ®ƒU¦¸zñru#ŽÄÑØ”¾ÃÞãëóƒe­1m_2ˆoUqlxãœÓn0ªâ VpMy¸Lš8è'§Ÿ^Ðï!ÁýOâYOC³M^yúû™8ìœÑ¸ûˆ£r›s«3·;q„ñbõD^7oB ÈHé(=´§r9q Õ–})Ä=n+gäl6ÕÌãN§‘£â`Ù Oo¯ƒ·ÛŒ\~'‡ƒwôöö÷]ÐI,7O°PM˜Ì$Þ`á ¹ñ‹³8—{÷âÈÅì»ZVkGïq€=§dM.¦÷²ËúHYs Žì¢VMε·háNÞ¾}çö§·oz‡|¹u«­-8C›‡Q8=üüó®®&N¯×ëtº¦_\¼?-Š™®¤¢Kªf¦ƒ3ßÑ뎞î:M—ñ¦Ó9åÙ3½ž{½í°^ÿ|?h2`³•€»úZ)äpb XPoâØ¿Æ#îÝ:Lï:ʈL}H;M¿˜Úß~[‘¶µ¶¾þú¥KwïsnÝzãÓ]YùÙSA8sF§?räÏ~Þr€È£I§ojÒëüã«gÏœ9{á ù|àßèõŸ"sé´¢PÙ¤ÓûôÄ3­­/àGòõ–ÈýMà%ç~lÇ ðC²ðBÆégŽ‘¡A¯·\б³›i§âPZZ˜8þ÷†8>{££«KQžFE"ŽVý‘£Ÿÿâê…ƒ-MZØ¡×ýøÇg©8ÎöQq´è˜8D‘ˆ#§Šã $8y!âPgR˜ò»ÕÊîÞí—Ë*›+rIÄb·Zy—*‹W1¨_uà7ј”›òâ…"/”Ÿ¯ÝA_ˉV½þÀ‰ö–ƒ:BKó¥KDDwïvtètÑèéÓZÎ^˜ ƒ´¥â‘#DM4’`4Ñÿ4Ñëîß—i¤ÁZW¥Órª¹•D&ºÿSgc5ÿ±°ßåòù“áÙå2â{3PW˜ÉìÝCºY6G¯¹3ó 5@{[ËK45q|zûöÿÕÄÑM>ÝÒÒw18 ÒH‚‰C§}è7N“Ï÷ïÓ|ˆÂöšMÓæ‡¹V–Á«ðjSRÍQ°Àj¯Ú§*r|´\4"×<™ßv\¯?ô£{„[w?ùäÎmʧÄwï9ÒÜÜzðìYÚWKx?Õt¡Ó·2kèT…´¶Î0±Ðn¹4Ë‘Jî¢à%xåY¬Àðv!‰— „Fk•Ùñã:ÝC÷&ïÞ»÷Ù'ŸÝþÌD·ˆ8Zš[ZΞɱI(*„§QFØ$ïzRG³tVVÎu0qDO¿IÄ+ðÊ£ÌW2ÇàèÎW-–Ë‘“°£F›µ·7鹓—ÿÏÝ»4-~ûÓ{wïÜùøò‰Ómmz}4*ËYY„¢QÌáÃ4«ÑÜ|èÐO~öï_;~üïéX®£µU&¿€Ô:M‹ÓxCx´·÷CÕ±¸wêÈ {+̡Ւ¹oo×·rÜåËš8nß½÷ÙË—-'Úµ¶JÑL†˜ƒzƒå0¦It*ŽŸýìß?~üÐ!½ÐK×X G*uæ4LðjÆ|…µL5×m¿Ë í -l—íPäܱc[Ô|òË—ïÜ¡îèèÐëÛ^¿w£µõàÁY!§¤Öi†Cm¥~öl[ûßyÿý÷ÿÇèû££ï]xí§j†üiz ªzó ]h…W ¼Á/?7DFé½k~X9ª¡Àý—ÇZ67ŸƒªC–Ïži;|„ˆc”ü½øÞO~BÓM:&ŽÇ™Ÿ7éšo@Ñ›ôJ?vÕòã12èÝmþ…ÎD>¨ojnîè¸|ùöí±+—/ÿ˿ܽ뻦×]¼H#‰«ÏåhT΅ÇÛtº×^{ŸªcdtôÐ!}S“nfšªEΜþ{¾½¯(e½¼:¼{¸¾j¾rÐñíö×>xÖq8;>¾üé§Wˆ8>!⿦Ó_üO4’P4q¤¢9"Žö6½þ'¯½G¼ñ¿HÔñ£CzbŽéi*Y>}Z§; qÀÖ7éå+(È»þ=,%®˜¹¯-Ò5éšîÝýôÓÛwîܾuëÚ½>L§éTU–µ¡ñ‰(Í[[›››]¸øþèÏN—ä*99ó`¦¥“TPå-þpÙfQÞ‘={D‹Í1¼^CI ] u€ˆãî=šéøìέ[®é[CsT9eM+Tr’œ!âø·‡~tDoþ\¯×5•®ýéÁ–&To@†Êæö®kn|ø9rä K×áÃtêé|ç'w>ùäα±æ–ÖÖ¨}Æš¬+´'•,+kéôýwN´h9zäðáÖÖ©Á`ÑÎáÃxQ@Uäoyu,îÕ„ÕÈPåÅZ櫎#âÐ5é:Îm0q´43qDI¬!SyPuPq¼s¿½½¥åÈQ&އÁ ­$‡8`[2£ƒÒä{åŽøh¥ £ÖýnÛÛmm¥-Ô/_¾t©­íÈi]Ó‰Ó33©Íu(lù=n.8}zæþôOJÕhÆ«jb¾âôÐ^¹CY(_Jî­qÉ—‰ˆCßJ÷úSÅqô?èu'ŽRqÈÚ +µ‰b(8==3}ÿþOOÐÖê-Ôˆ\9'Íܱµ™Å²:«ÍŠÖ–½½]ßÔÚªk:Õ¥¶2ìzóÌ™³}]oŸ9K9sæðßéuúímx ÀNÍQqvh/ãŽr‡wt \»®©•8£«‹n«Ó>M÷üëêÚÇaZûÑqÀNQªÍWÑ ^‡ðºÀLù0hGCÑb%˱S'ØVãMÍùŽê¬½úÙ3xîàEÔ›™iU?Ô´U…2òvµPX‡úí«kbÛr@ð\T¯ÒfŠFâ/_J|±âCY|î[—ñLÀ ãÛ¡mÕ1èYŒ¿ <F†à|Çð<‘Ö"VãÀ>=¶iò1²¨–ˆÏcQìûÐcqÔ;8ø’V[yÕFŠ™;+™"CŠöeàÁæ­^JÖchd¾(‰1ü¢[㨧X$ž/ gáÈU¢’ò—Œ”v0,Ù"d¿ch´P$§nwgͨˆC†ËË‚µ²"—y :$ŽliÖž)n{2œÁ¯^0d•D!l^kHmž»/ÿåõ8e½Üu‹“!$8D*e¾=´ûÝÆ¼‚g8±€ªÈ…ÇHŒØÆEÇâ ;8PÁ`;ЬÄPùp¨õ¢‰*/2ª"£G€PÔ½ý,©P4ŰJz­c»XÕ(Ùõop¿U(ÝõÛŨʰ»7¨’Ä8voP %^’ÇîMªQRùç…7T¥x¢Ê»¨`).€*ĽXP vJ&ªFn¨JñDÕ0ZT¨Jqɸw€*ÄGQ1 f”Å’U˜¨P-Ü(iP58ˆ •-Õ†U*R² —MT!1 "#[æ©0Q 2Š·dš +ªTc¡4¿áÆ/@íâÀJ\U)Ýc|{7¨ÆzqgCäÅlCAoáoñë°ÊFÇM”o¨=æAr@dæm€½åÿ')çEendstream endobj 61 0 obj << /Length 779 /Filter /FlateDecode >> stream xÚÿü   & (*- ', 200. ('&6/:'2-/S>k>x+U",NG9cM'IrGVN3F8K+2oo3n&5UHR-QR8jmLk-Qp3gjlQI MK> >> endobj 56 0 obj << /Font << /F19 14 0 R /F20 17 0 R >> /XObject << /Im7 55 0 R >> /ProcSet [ /PDF /Text /ImageC /ImageI ] >> endobj 65 0 obj << /Length 1091 /Filter /FlateDecode >> stream xÚÅWKÛ6¾ûWð(5—‘Y4M¤Ý4q{Irð+^£²´+kóø÷áâí•l ‡Â0D‡üÈïINü8‘š(k(Sš,wFñMÚ I«ï_Mxèc~Ñ€é¦Ø2å’Ú˜ëÇ%§\ª’LµV ìù8ùø™‘@¼™8«$ß ÂhaKNv® 5¢,¢¥š|˜üufQh*5´Žåû1²‹EßCž 5•Ü„Ù)©¨)¤[Ùílró·DX0Ìì ‘¢¤ VN”â0"+Élõ1»{ÈaˆlÝλ|ʳmγ¦Î?ÏÞL^Î …‘´Ô%I~nÖ\ij¤SùóœN{™¸‚‚áÏ…€,©0ðTHIuiMêtêl)¨m|,‘G€•–¥kLËnê\Yéjš‹h½y½+ɯ L’$ó,JÊKK¸€3ÏMDcV¨--X¡že­‚“Кj êDÙ!È!&WNvl-Œ"7TÓS‰KF™4IG;ÃŒN—0-;†›}ËE™5iRfÛzµ† ú^A¸­1îj(tØ^d÷OP^ì}ež ›µk_i¡á©®1FëÍé"BÂ…¹@Zâ5FZtÃUˆaÒÆ0{ÒÆ1‘´î—»]mÂâ÷ÝIl»½§ñéá_X6u½^ºÝšºÆwYË&kº{o¾Zäè3@·’%ô8q©×q7\‰$nó@ÜL îî]Û Sß·ëD_0©å@Eë‰øˆp¬¶}î±ÔSÛµMU­W¾¾Àqøòm»ÅèݬýA 4t©€ˆ6×¾ÎE€'Jva˧^c"D7d¥a ³aEHá®ÿ_"ˆA£%ó‡óˆ‰×˜Ñ YQÃ"Œaö"Œc¢)ÜÛÆeGÆ^y)ê:㘆DRÇÎU÷ûØ#fŸ˜R\>öÒþèG;Iápƒ8Õx€ê² Rúã˜éÄi„èè… ×ƒ<h–S¬“<Ýa„AV!î»ûm23gËnɉ”"ÍÁäx|ÄSnûu^ÎÆ UóàÔt¶{õc2Û'¦˜?k›ý~»¨Â¶éÚyí'1áG‚Õ5ýýþml΃º.„×-qÑ-z!—fP·1Àƒn〠[Šõ. cé7Iº1<÷«^¼³ì÷óãM!³ªq/çÕÑPgú‹t²XxíÀ [KýìµôtÝðŠœ ©íå· ~âßµöÕä¥3… L9͵ÇW;N­R½"4‰HâþRKý×s2ËKwkƒ]9~Û–_ƒÏ?h¸ÃCàEo¬;Ñx>Ä;ÍËw¡ôêàX½Ç½VoB£õŸ?±¥ùºvÉk‘l’@3cöŒiˆ„•=Yíu¯AiUÚØðļŽW隸ÄîÁ/åXÜœMß(}ýÌcÜü[ÑHVendstream endobj 64 0 obj << /Type /Page /Contents 65 0 R /Resources 63 0 R /MediaBox [0 0 792 612] /Parent 60 0 R /Annots [ 66 0 R ] >> endobj 62 0 obj << /Type /XObject /Subtype /Image /Width 1593 /Height 612 /BitsPerComponent 8 /ColorSpace [/Indexed /DeviceRGB 255 67 0 R] /Length 60229 /Filter /FlateDecode >> stream xÚì½oPy¾7àà­­ZJ æ{úRîtÇZ8Â̹•„Ü%V9/f@Ô¸:/Ø{‹D½¬ž¼HHºjä•To©Uzêº0GpçÝ}J¶ˆ÷lyüæPTž9w9Ž3„, IÓÝÜßŸî ŒŠŒø}„ôŸtw0ÝýûôçûW– –@‘^³z^Y\+.œþ²{xF”ŸO^8 eE–$YÁöÚ~Š„f#¿Ák¤‰|Ç›‰6äi›6²ã©¨.…ñ4Ø `Æ~O”ÅÛv«Íf³–ÚKK­Ö=z.œ3ÞƒT6¸eF‡h:;ŠŽó¸4ˆw/ ‘o™€€€`A”{N#i Ø¬6´FhÆïœ¦#’mfY±ÛЬ;Æ2ðcd£‘5f²¬Áj0Ø‹ì{zÂÖˆ‰#"Æ‘œ)%Šƒ )Ÿ™"¡ÐŒþ‡ò}¼ðäöLj@Äñ»êz@ÒíâsžgxÆÈd0Ì&ƒÚžAQÔŽ –e–µ±ƒÍ°³ÈZdø(¼ bƈç ÂI‡ÈƒÇ9ëêœNg­³Î?yâa‚7†ýJl€—$ä°I‘ìV;Ãä°Šç–Íáyþx6ƒÍawò0äpÙ,›™]Tj7dÛ,¥ö/E%ž1$BÉu½÷Ÿ8Ò@m{Xá<~¢_!_Áëp:Œ&ÑAl®ºm›DËúF©÷¶½°†32eàõ†&·[¯§ôn·Û'4ê·ç@dì¦ØfÅ1 k`‹ì6«-[ºà“mŒ7 ;•x9’!Àu1®ÄqÝY·¸ÂY{òÑ?-Ø¿sOÃ^¼÷Ð [öKià` [C1wNàÀ4G/¸yžæžÕù‘A±”×§ózý GïþåùŒžbpŽÁZ|jR–ƒ{Dì8É×L µQÓÞ®ÇqÅÕ@›+ÎXåª ªÁ”¨‚D²O£‰‚âÈ(”‘»\¦Š #s†ÕXî´4™=ƒXEææáö¥fxK)’2ÝèÏ¡ŠÕçÐîÞÈåäp@©°FÎ`µ³¢\ZÒ‹>.Ú@¾r‚õGä¤ðƒëhíMÀ‹*Ãy#Ðoªª«í ÜDDr‚X qÖß„FÔëCÒÔ0)E/|7c°f¹ÁpË»YC‘AO]”åÉì[h·çÅ,šf›{Õ”ðHô¿yÂREó4 HƒâaÔ.ÏØìÆI9²GU3w‰­Š`Ýñà˜ë Wí@WŒ9\1žÐPÞ¬Go#¢ƒ€ %¬Žä"Ròô´8.6±a`Œ<Ï B1Ü~crU‘"/N•¸’4 ×3<d‡žÚÁ3ÃBÕÁ³¥„ Öó'ã½8®ªÄQý—®@­kÉ»ÕêŠä›# ˆAÂ.(Ø&í<` £`äx¡IüÓ"`ìýÐÔºEûEgTî‘ñû¢¬æà9¨=rp08x£qRÝÈ}‚õGèx]¢#f¬rµž8ç+wB=Ò®®pž$¡¹[3hüÈ¢øáÿ¢ª.F{Ìpÿj€*# ·õ4ûôt1 †³ßâqÿ»Ó8ØÊÛ©Fj´àÝ'-4m‡»ÐàÖÓGÑ´žãla£K•— 9ëÎ ¶(èÖèÒT…«óÏ‹<ð»øÞ7±Ç slEíÁSïY4Yð]@Cú°×Tƒ"‰< ý<#>·Û­§8nE“Êq¢ÄŽ$À;*.ìÆ’Ãf)AË>p”Ü‘£ŠrC΀à8õSÃ_NS@°ÎxP—çM@]˜8jo€ÙÎEâh‡ouUÇmL[Øâ¤„#’HFÉ øwÞpÎXÌR<ÃsFÎë7rÍG‡Q€.¬9¢Äû'lÝZ\5£.Däˆ"{½Žf˜ÊÇr,Ë8¿Õ6&N‚dã DXf¸jàì 5ÐUÛ y#Ùq¬Ÿ|…[ ó1ö@¿‚`¨´`´Z ™, Ç9¡¹¹‘óÿUÒ†yÏEäD²'Ò€"G¡z„3Ц–¦z1ŒÑV)ëÍÎúDK ¸ìЈãR5ˆ6ꪻ0o\ãÚÂ[CcôDñôvÌ·-ä\öö¢Ð-Ý2š3(iãÂEï9ÎÏqߊ‚wF—‚ˆ†|ÏÝxÑ"óçã JwùFœ×aá…K“0<“ɽaвÔÜDÊ$Þ8ZÝY{Ô•Pdðÿv¦¨su ª@Þq 7‹nÖÆ9үװ\‚­A© 5Œ¿AcþsÕC-5 °¢¡âóR·l¨®Ë,{ ¬""*¡IArãôm,4|¼,ÍHŠò‚÷ iZüúýgýþblò%Êï"ÖÈ?éÝ4Çr ÇY#ÇÙFÚ™&§‚`½£ÿѫ꺸À©öÀ®®?cû¶MõáªEžDC•³e‘#r‚­_èªÞÕl[’äš¶1–e ;Y«}'盈’¢¦iH° :˜=^¯÷ úmð7{½Í~7§çÅSÔ)½Þÿ?`…íóÖMÚÉ*2rvë$ÑIÈ1亸èŒSWXf ß8šíDÚC%Žo¸ê¯:(*—|“› K%¤%){m–Td%*Éwað»“Ù mR@iÈ1çàQD»Hb  ú/ž¿Çí÷û}zÁ­× 9zNÀy€â<<´ ×34Êá0€£Ú¬…Ô§"HœDî£Õ€EÇuLuu¿«ÁÆ©µ`“.Œv­r.•EH& ÁæÂ¤Ê Qì×ïŽ ˆÖ/àÁ»ÄŒø¨·ŒòL#¬w›Ád2,ÃÙ3%«·ÿèix ñ—9§Ï~ë|Є·ÁCÓÐ ~ý~‡ÛMÓ€=) ^düjâ¡â 8SÅÙKKìrfÖ'4íÐ:kTB¸ŽÙ¢«Ö¥úÆÊ¨Ó Uj„•ËUkZi)äÎäË$ØD,%*+M…••UU••ùŸUV:ÀlynK%@Uî'8uTVäæææß\Snž)¿  ¿¥2÷“ªGK‹ÃTP^+ªL¦SazQ_ ÀZSA~^AAn)ׄ– àqM`wGág&Ìß`³Î_ZÒ+ݳ[Áº#³NáʆmXKtªÑá¾q˜ X£ªTª¨í€;tÆT qsl*,eD-•U€8ª*[_€ÙÕh ©ÄdÊÍÍWòóóMŽ*ðØ üà§¥pC!`ŠÂBø‹X-"Ú€Lñ_À&À¹yyy¹`0ø4´oY! –eìF£½ÔjÆÎBë å¸s±ÕÿÝÞ¨œº«]µJq]ãöxÁá:Z}Ià_k“?ˆ›ƒ`ÓÉÙI¢JT޶TA>©Ê‡Œi ûà]¤5ZZÀ»H»þ‹*0L…" !¥ª¨(„z®’æóЂã0ü„–ÊŠ–²‚ü\€Õh0ÛJ±qLîUÈCA2ªpÜmW»ÓåÒÜÎÚ?«Žë19ZWw´ö*N討ïðDbr 6o(˜0ÊË1aBpyõѰ212íùEv«åoAÎoò‘z#ëJ(1¸t LùìcØ_¾”7ì%ÄA°î˜9?ÿöÏå’ìmBCó‚/›Í˜8ÊËñõÛ¯¶Þ›@™yò ¤ Þ˜|<ô€2‚™cá1xyJþL?Q[û !!¢< ãÌððb¨‰¡¡îñ°éøøøÄXpÌŒ ùùùååv³ù±$–jÆ3Rt„`‰ÃqøÐÞƒ æ8p@Sßœ9ì{ÕÒµ÷0!‚uÅXkŧeû*-­m}}Oßt×{£¨$ˆÒ‹C øîqde¡]Á–* yhlh¼{|xÚ–O†,PÏÑÖÝã€;Â’ ³Ì‡b dž†¨QH𠈑ð¤€à€¶õxðƒü¬Üüs=á ‚õ$ŽªxWGùA0ú_Ri`_ùÛJŽƒKGÙý{Ëcï„8ÖÁÜÔöé«(ûæåëwD¯‘x‚Ÿì+«`â€Þ‡)tÿ´ÊÁ+¹`H¿Ç$’Sð¯jSä§}ÌËÊë¢!,Kc}m_Uµ´þÕn• º³î- ꫪj÷ CªòzgƒÃãùh£òñ¡<ð!ŸX o¬q¼º:š …Bsßc< = =|øpêû0»dùY¨?šš›{9Õßöëz6êÓO' xKNx£kýÓe±oêuvªoUÇE×% óª˜Ù~˜8€P¿6Á˹o.@~nprBQ¤…É>¸¾å»…Yä 1.Ýÿj_ŠºÍû ï >ÄÀçh¿Šnx°1SÁ® ÀCcW²L¦Ü‚;@}Œ ÝAÛäõ»‡²²rË¿°2#IC¡ðUÌ"Nx6;V„úç¾3Ì!ÊAûΠcÌõ÷Gqü”1555÷z;Õ§+ ìuûži‚aLr¯¥-~¤ÇCµÕ ´J’"ÿ1.÷UbÞÈËÍ-Cíÿ·aâ(„iáYyŸŠè1Uµä›`âFaVÖYŸ«’#K@CSŽŠ‚]ywÉtg™òLp¯ñ`p¨mòy÷ðXL«ò É'HâèGxøp‹‰ÙÙÙ¹¹É‡¡go¢7¾Ÿ{öL{#öfBkÌO¯}…ý•¯Ž•ˆãÓ7½ƒ°g†JVŠ,M¡é5{€(i…Âq¨B#Ž|GÞ¯%:`¢_é /zróZ-`óŠÂ¸íªæP%ÇøP<Ô?ﺂ<ùUUùXƆUÁÑ ô´lµT-I! XGâˆôkÄñ04;G³ß¯³sðx„8Ö/ï·VÅÿ×~zëOWÆkÜñ¡JÚ(-j×o•ê¡@Hª*¦ò?ÿ‡B¦¬;8<64d‚ñÌÀظÂA.‚ä!ŽF¡þ‡S‰Šâ‡W(aöÙOщªQ o ÁŠ Xǰšªú°nþ@Uš’Çó‘Èø  Àák@´Tüa4< ôÇHÌ ÁþïÑðHø?žÃÖ¯WBŸúЕ¼,S~îçeyhù8*+IHÁúGj&Ðwx1ÿjŽ©ÙÙ¯Q G‡šÈÑñý÷ÏTÑÅ;4É’?~òŸû8X¾÷~ÿÃGèPGú‘w\Á‚£˜ªV‡•µCÛOíÖº2qŒ½ÃÝS¥¨IymŽòòܼ+#8kïsìñ¯r¥µ° ¯ àá Õ;€‰¡' ¨"<2Òçp8* ÿ€GXj­Ä¶«üîðÄÈ4äi†óæ•»MÐŒõIp 6Ue¡‹„;Ö™8PøGeU\ÃÃæ¸ŒrÈ/@ÌqéO€!ÐìA-E®›Åæ)øFÇì÷?^Ú_„FïCè+A æ‹þGMp|¥â XæËV$€C«ã›Š7ÿðøAº4НßñhEê>T–Ÿgʘ@KCåUå@¼ƒÍ¨ㆩg|hÖQ°¥*k`aæóFÒ¯›É5A°®Ä¡6¦©üâ‹ú#G4ÉþGû0eì¿ÔpéÕ™±ßåk—ñì%ìçø ‘¸\¦ª ècÇ{‚N,8ÆdB«ÂØÊÊ¡eu®U Žm:Är*Œ¡;y¹¹¦<ÓÒCC¹Uù¹U•cÊ‹‰ÜF|ñÉXPù#VCa@Ó£aœ þùóÈò|«j«ê >—X‚àee¥ék¥qW¼â X_âP½•}úC¡zLwàÈß½wo,«þõй(­üôkü Íøör|*Ž\þ—òEÞèxêA(¢àÏh• q¬߬ÚW1VöŽŽDèqäÉ Þog}Ç®á'ã¸|ˆ¶Ÿ{D‚ãá¡á ô…È×p²F–LÜŽ‘׎Âêêà±ÊQ€2Ï+†¢²<€H(¨|e*ÈʺGnVù¸¼„8s¬3qŒaÞ¸¢qCØC^s9îì-/ßsd|ÿýØ>…¹âûËšã…ߪ¾}ûÊË„ÔlèP½ýCªà˜"ÄA°J´¬L¯Ë|9Özh_YYT<]Í!-‡¢àä½²òL¦ü‰ð «º’‹·ÊáÑ‘+Hqüq:8†Fþ¦ ç~G ;ükl¸êŽ"ÉÿMKaa çOzCþ“ð!q|޹qðG‚¥C$—`ý‰£« v‘E§ÚqX‡\…ƒph…Z-SÁí/?÷r.„Ò U?|yŠ Á]“ q¬m+òÆ7o|Œù—SSc}}mm}có«ùæKJ¤ôm)p$‡œà0ŽrþL¦'32,b› 3üZú£á‘? ¦áwŸ‡Qžàµ*èþÎŽ…Gÿ¿‘Q”˜÷ù¾+ 4â¨x!ËãØì¾b2©±VY¸<Ê„Z1¿‚\ëN­°¤Nå}5ÿïlå´ ‰Ëâ4  L ?ªÙ—â‰ã™æ0¿¬Ö™› Q½ã¡V”^;Oˆƒ`µXÑÇñÕ{ü#JÌRjªN% æéåA‹“É4ÍHÝ05¼¥e|dôIx¬4R00<¬Cs ¢$=€UÂ(D%Gz†DXnhÚÕƒ«$Ni%ׇ'LZ®#¤8ÈÝC°þÄ¢ «æq„æ*÷ÁÐZÄØ5þ£FĄ̂?4Z™Uß½üƒV¬ ¼þé`Ù…Žönl Sq¬+Ô)ë{¯5›DQ§K…Cy•¢(ãÁ8_è¨jy¸ÔGžÃÑr7<222€JT¼Tæ²<ׂÜߦ;Ý#ƒ(D$r6ôˆÞEµ¬ †Ú i“eÌ5CO&òËsÕ"UèÃcḇ‰©Š`݉cNë Á2"—h>o5!AåYŸ«E 5ç8!‚u'ì‚?ô(šš:x`ÿ¥—àðÿ'¤8.ÇêO]'ŽgªáªO­”K l×Ç3ÀB÷íûíÞ;wq<• q¼¼­÷§ÖáoSt:è㨬xü$<¡¶u´µVj©ß»†Q¿ð¯ !ä}£vðƒ}ÇŸ·ªv'ôƒ@?9ôc eRh2ÝGËÍkUÆÑ^ÁÉÉ;¸šzþx·<9)-G¹{Ö8ú´HYäЮ߻÷·¿=xp6VH¤C«‘>«IŒÙï§cÞð?ÄÉÕ´5‹Ô¬ÈŽÕ{aßñoB+ã ,N}jÃʶ±u«)ž®GþÄÈD88€Fö–¾©Uq䕇úŠüqWAAa^²:‡Á/üü…Êå€7Zr¯ âè†-S^á•1¸ò¢¥¬pP5oMNÎU•ÁîŒ ËÊB¼â wÁºG[•fªŠ• Ù{'š›ûDj=ªï§gâØÈ,¬•‹MS—:â I’Ù¹©IDCwðá¾Ç)â Xž2æúÚ­÷ß Ð©àéz6¢Ø®Ó!çx¾iddbbõYyYSòT+"ŽÜ+£ˆ7Fþl+ÚqŒ£ÿ¥"£ÐÚò-­}¨&bwwÅg°îm…jк’•UPX¸ óÆ"Í9ò¡*T?žAÒÇWUjŽêÑjU=|ØÿEyyùÁýÿóY\‹‹ªz«åî×Bu¯©~ñË(‘ã>ì7‹s¨UÕ"U„8^ÅSØÜõ³wHÉ{rHIKM…Š£<+/8¾Ø/ü…ÈïÊçWîŒ uC>@µ#áèÐRô\›=}ñbDÍÕPC ÀíîFf®­-mÛaX0·gh ¨þÁT—Ü=ëN/Õ¼ñ–§¡GAœýW^7èÇ:ŽÏªù\¬uø'ä9ÿ7mýŸöï+Û»÷È£‡ý*q¨æpeåû&YÿÊÞ<½]¸äÝqª\ñ9xoPÆ0"À0éoddfèU ŒM2T R¸ª..22>Oy-GÕjúù$™Úh]Ó’µ?#‚–t7ëÅT ªR}Á`·êïŠ >‹¡î‘x»ÖAèý?‡Ž W‚Cãc€6“&'/ȸÖèlß!à@‹<Ž$EDî9 Äfµ£¡?3Ÿ¦#’mÙ²d·Ù v†50 Årcd9£Ñ`dYÖ`5°ö"»=¶FP¯ûˆ¸D¯”Jëj•_ºç±‹ú®§îן¸hcêÙ¬Vo ÿj¼ÿ~¼öß;þô§žÅù>–ÁÔCT÷r:ôr-‰cluà’ +”:_uS¾Ÿ½– ºuÍ–=Ûm¤àZUYÁáá¡;(¯;o †ËŽwCêè†4Ê¢;1Ä‘·ÐøýÀÀ†ÃÈ?>ƒªÆŸ?™OL>žœT&%X’*7¯<«¥µoN~ñ\ŒwàYÔ®“\AÉŠ<¹ý4ØGÆïjœ"IÒíâsžeŒ HƒÉd)*cÇ/3¨Œ –ÝÁ2í™@*lÀîð‚ˆCz½%ìýÇ”–7.ÃÔqh]šÓ2þ´Â!‰xö½VŒê'ÝqàÞ;òZ‡cåúÚóäºÝX±¹ÒÓd $¹7Ü» Å)¨°axlÇdh·²‚ò7FÂx‚ĆGÂOàd"y_=„% öàá£#˜JŠø¨çO&”8†ˆ›Æ?eâHjÀËATbW 3m·Ö0Pz†3ò§ø~–ýçùÏÉ`sØÝ;€áÙl–ÍÌ6– 6siÑy11¤¤!Ž1G LëÆ—'T s¯a„•1‹YE]x|çHý¨¯ÓUÖ”8*VÝч YìT+À²d¤~)îž ÆmÊ3äÑ>ñ&–aÀ<Œ¥\ž€Üž†€üÜÈ;*J²8ž™x~''‹“ê¢~ вÜcf fOVœ£³Ä'ì¼mlºQì½]Tdg–32mRMn7­ÿ•Þívû„†ÆsÒsrr€îØM±9Ô$¬¡È`³Û¬EÙÊ…3¸ÁüxóœHëéåXº±o\ ƒ¼1¥I‰gqv§Ù%ü°<æ o$öv[¬ùcyM‰cåÞÓŸ¾ö }mm­ßŒÍ‘K|}ѶÁ˜_šÊÐÑ!‹%v–¢ L¦‰‘'Š<‰ƒ¤0B(#£Q¨/PsŒŒN„‘âƒÇЙ0æ‚È#°= ›L<ÃáIL%*âÃU»ÌàÇÔÇjÎäÚÝ=?"ß¹§ÑÈ.œ¹‡Vز_ˆÝ·ÏöØŠŠ¢¹SFŽb˜½àæõzžáõ ËRøñúô¾œ_ëõÛÙ_³Ôæ9l“Ád«ÝÀ\Ô«0*Jëm¬Z¼øZ[oÿ5!h¨š”1;÷,FÏBso¦8žM!¿:¦œÙŽƒ{µò%ÉB÷µ]Ë*Û{¬#V:IšŽ#™Í¨UŸ¼Ç ‡;(XÒÖáø¤ö™©*ÿ䓪ʖT½@j³&È0¦üBSEKKUKU¾ 54«,ÏûU:ª`µ‘BS~n~~y>ìmð~ÛQ˜îʪò ´XY?%äJJ ëw>zWµX…‹‹YÔÆâ Ãñ K}$|˜“iäΉⷜŸ8¿Ÿkâ3¶çP•#¸õ<+°99,e;ñ¬ÑVdçŒ @íJÒ«þŽõ$ gÏã;6Ò‚cþä£ÄÜÜìÆš|ccUèá#Às¸$⃿-ß»·ü›²¶ÄñͧÛÞfDz–¾1r©'q|•¬>*EZ@£ÂBö9íq›ò98ðð^UÕRá¼² °P1äÁ‚éù¦––GU‹ov{UJ?&S~y¹V¾Á±„ b Ä‘ü€q´‹¦Eñcc–Ùyª‘c8Ž3íØÁ7ÎG%y^B ó/À¦wi£‘§r€É¡ésxcNŽ>ƒá`Ø•ÁZ|ÎàÕw"& qLá‹R[ÿ0ôìû¹XÚ7t–"˜]Á¡ñ꺹~ä^G¦­K\:p¹ãǹWMrïf¥[™8~2•ãér{8ˆ[dÆáåÎÄgÉf§Rî`^–$Y‡ÊËü£ª–ºE7QEa –º5a¹‘µø)\ä¸] Ø ö%¯ÈÏÿl×.•6Ê‘Ãowo K¯W® r% Y„§ñåÑŒ³ûGG¡4.ð;l™,ôigÌ6[‹#w^·N¢0[‰@Sj†Ã£"ÊÑF·žÊ¡X¢Ý€98–â¡§ƒç V;+Ê¥%(ÿCŽú’„8–šé澟‹e}æ@Þò·pÏá¾å¡XøÕìl(²ì§¿C+š-äeÒ*L\ŸÝ'W(Çýd3S‰ãËéÅz–”fn)·­EŒÛOåbÞØÕR†÷ “ Z“ª*&ÕDUUìÆ U¦ŠJÕ<…ˆ¡¥"¿¥¥¼Üd2í‚uq óËsóÃw>)GïÎ-GRQHˆc#ି )êECºÔÔ0#E/|÷·Lk6”@iðnÖ`3bãihÖ[h·p1‹¦Ùæ^lá’"Ñ» þåP4OS€4hÆíòŒÍV<)GJÕ±ín²dŽ/!ŽÐl¨?“8õoy¦ø!Ax<›Å{csÕ,f’Лú›âÚ Äñ“0µ¢N!«÷W¨¿5ù2Æ¥zK¼§•[vä“”-VÑÊ0Üß»©<ìÉp@ ápäVÀi4U&(ØUQ˜ýù`äwT¶˜LUÐ6u¸¼üXtT–…‘kBÈ+(/ÿä“ ¬4*ªˆ0>9„ÔG~!2m9q$9ÂêHþb/†ÅðGƒFPqÆ£pŠáE!Ê8P.>µÇéÊêÜ€^¯çyŠÕë3X–¡(:8îËâ ´hK2Å¡=î…¦–ðFÿlŒ8f_5O©«ߘÃÄñPÇìäô°âx§æ—c_•©•¸ß<$gå¼Á2â'ÏXŽÛ–”¾ ±Ý’8oyIª_°ÂT_ÚMQ4íñºý¢ú¬ãtQB‡ÈÑ´ž¡(Ž4m7ÊzÓ£û~zÝÀózŽæ9Þ`6›KµaCZ28H’"’+e`œ3E’£ðÜ)ò¤ÝÈy£`¤ùÆ&÷O‹rDIL¿Q¤….žk5\j€å9ަ(½›‚z…á˜LÆè·«QuIp=¬({—`.Eu`ÿÁß–ï_ eqó?€ò¥¨ZÁÁ÷Žÿƒ±û}ß´µVŠ ýôC«Ò÷)É8O,žŒÏʾy™Ôêâx>“Ír CÓ”>ƒv{=îfYŽ&lÆåÅpT„c‰ÿ@†˜Ù¾Ã<` ðKsL6 z¼ï£Ä!A—:"H&u‘/Dñù¹¨žå‹Qó4Ã_ükðy^ xÏŸGÏ}NçÙoñf߯çÕÛ&óR£ï>iÁµ—›ÁáN{išösT­7r,ÃGÎhÇÛ½“—8*— üå*!\ÚW¾weâØÛnÿ%8´R”ÈÚÝÙsSc}S/_WYýéÊÄqˆÜïZÉ‘²kIM".8$O@«•ÙžÁó Cý£×ãmðK¢-ŒØá¯Â4’3œ€YbWú›Ec‰î& ¤Q1Úä?EsœÞO±aµ.˜­hHYJ>uÁr *è]ï4¹R’ Ej‘do>O¾ H+ŸñA’Ìð,¢n‚àv»õ”Ÿƒóh“ÅV —à«iÀ{=p\ØÒud›¥]->Ÿ$GÀŽ4G1T†Åñ"[•ú©á/’˜8–HŽƒ>([™8öi²ÿ€6ÃUIb¥U>#¥J’ÉZÕWYv¨u,Éû¨Ô›{Z(²‘ÁlÄ´U5{ ] ËqvVÛxý?¯ñE‚¡ ÓÆ¸Ôx\YÔ å1qr ,Qp$ qÈ­+‡Dn‰õº“òs(’xO–íÙF†œá¦3À‹§Áíñ«Æ$izZý/‰hYŒh·8 „ˆ,N75{½Íçܧ8?o/±˜ÿ=ÆQÂnwILü2bî)B®“d3Z¢ó‚~øB v«Õi0d2†ÿShnnäüßEbÛKdu96Ò„5ç¸$B¢ =Í  FUÑ4P/¬ÑÈœ²?fÐä¸_V0¡xŽòxCÕƒ€Ê¬Vû÷DN(.ä€j *ß÷Š›£üÕÔ¥u#é1U¬æÂ‘œÈ6ŠáqÀÛÚKû=þ‹š_3axQYKó•^þßÍ Íg|^ýògŒÂ¹Ò’’)6ø$ûÉïÁæéV܆ßá²·…ù¶1;“g g¼pÑs‘ávrß.ÞX]1 Øâ¾»H?€Šùó8¨Bu‚ÝâÑf¥–…Ç+ È[Ëùv Z¥æ&)éÍ"ê48¯ººº:j\pÁÕ5ê\ úhíM4´…›Ô\º®Øú“é?6_ñq޼í3æÌ Žy±9&ƒÁõ@wpM»Ï¸a•B Jê•Çq†–ò¼qî(ÊR”e¡I|‚þCex#ÏÛÌ+v}Û†—}œ%H²K Q­UlGçè¹ê¡ëa£yÙç¥zí,Ãr¬ÅÎú9Áïåˆ€Ý Òé[P„JŠO˜W  =Êû…ˆ2- ~ýþ³~£äÈéóhóˆ5rVïæ8Qµ“å8»Ñ¤àí:© û€ ]Õu˜7jo¨¼qÝÕªGËU­Îb²p9ÛÁ•9œ'’Ì:rßñjor·#Xw4z=hZ¿“a¬V< 2øõíÑ €¡ÀÞA¼‚BcDñ"-‹§ ;rØ–õ ~_±þ×|£Ñ'4¹yk‰5h·Áí"rQ‘B¾Û1Ï좨}T‚Êò¹Ÿ¶1†™ ϬvãNÿØDšWÔÂè #½g¼*|~h¸ôÓz½ŸÒS9_êÿ?mb‚BàÝ4ÊgYÞÈÙ¬“ÒFy¨ˆÇã~M ¸Z«’€«CGŽÖ¨³µÎºÚ8â¨sý®¦3¸Q‹÷9™|ÿ·¹ûß´ì{ËÀù¾C‡*ÛúÈ3áÖÄ‚¨P Œ@wœ§Áë|.ã^m²"GÇEqúô)Ö¨ú‚-f…S¿...¦€èhhð­ÖÒÛIÔ’‡à'ž0W8Gm­ˆ bT‘ïf2@l° wÊ‹OE ×KÔ<ç€eà.ÑÃ~À‹§Á мϧrô45 *d( z=CCÅÁ˜F›uåóôãq¿3póHÆG4Þ訫« À…ÀM'2`¡ùöEóTë_08'«kïåX[e,ÿ£ìu%’b9U}/É=µ%­UÈ¿)1LÍÏ4Ù N =¯Ï·»‰=ý²©I8{š1ðÅ9 ›ÁÐ9|N“°ƒÍ¡xÁçoð6øÎÙJ¬¿Gm)Iâc^ÅŒ:bi©ywGpÔ[ý ¼Íftî^œi‚mOà1‚e2og ’ÕŒËâ 4òkˆ¿Êùòì·Á¨Ð˜Ð4çÇÿü´ß£viÀ°>¢·Y¥š|z 8(˜ÃÁq¥¥%’Ͳa¾ÀN'¤„MnÔ¹jºTÞ¸ ×ÝTÉlUÇmK§ËDGMÒ98^Õ}m‡ú^çsJH¬ì#‘»[‘=¬pÄÈTãêaP.å÷Ãr½ç Ìó‚ÃBCxTd)c1oä#ÓÔh4œ3»Ý½Pl6[†Y – ]5‹Š£Î嬾Q[ç|°).¦¥yƒe_Ûôk%]Û7}›©ì°Tún;ª`ÇhŽËàh='°z}SŽž¢ôzèøày£_¸øí·ƒÿq÷œÝ`1ÛJJÍE@fdêÕa¹7„-àƒ$5<©°Œšb, VQ•ˆˆ»KÒ 0êg°™,x–Ø™i°[ýƒGBx9e<.ÀQð[§ ¾ø|nÁçö¹Ý~=¤ 6ƒ¦^Ï7¡(;$I1q°ŒÝ`7–ZÍŠ´$ $É™£îHí"ÔubƒTW»„«±" y¸á¬‹‡«æHÝæàå²?ÊZ‰7ý§0†»ÂWl¢²ÃØRq>–a É eôÂÒ§¯§yŸON.ž$Q̪ßiR\NˆHÒ…äæ¼a(anɱ´”‰ñ[e3ÍÓI’_5zÁÊr Ž =MÑ/,8žbYª¨hO©UëÝpBÆVň#p=Γ:Ô@*” 茷T9÷ožëhÅúVûH|Õ+xéXò%=ÝlÿC‘›­fèï€S4f­'%E1[BèF·X`+ieLÀi¿ æúåïþ‰Â‹‘PèFÿÏç’VXùB¥¨/i‘`È cÃK6†ùªb;V¸³F3ø‡óþðÀ¯Ä%¸´™ùÊaÀÍRÅc#—ÛtôøhÖ^dµ.,)oµq:î\5ñÄqd1?¼æŠ/&‹Ã¼¿Íä\±ÀÕ§}$ºj Æömnƒžˆ‹PEĽ{ê÷DJÌ%fD%½ÈªG… î(-GæÑ8Š®œÀ%.ò†n;q€@Dœ8~ $ÎZlõ>~òăŸkD8D\À3çÕϫǎñ®ÚæOg˜L` Ö`Ïd.Šßâ! »Ù%½È)îip{…ôe‹ÂYt]H>LDMÈ\%N{a€®×íõùÜÍñ™³¨”¦×+ónú\1ËÛŠìæ1hÀš¥©aÃUל@lð»v” ÷–ßP“Ž×XôÊ6·Jj–—$ÅdÔG^bœŠÙµàÚàÊÏŽÛˆc÷V¦êΛׯq.†õ»ª5u‰~ÕŽººµ3hD¾UOÐc,*Â<>OÓð5âÛ€àhÀea$ÝÓ™ ³•%±¾d»<ôx½à­Ô””tI‘/^DGl ‰Ñ œINr·÷Ü·ˆJRt4ÅҪ؀‹^RÑ;ö<K,›Áå9q¬öz{üó8‹µñEF€’¬=~bÓæÅ*c­e›üiúÝ0·¬ÞØ7·Éþ›jw&YŒ_¡-Ä|¢…^jìò“ ›LUº”[ÕHuÜé¬mï Àã匚_5päwuΓkEgšÐ™éµà§ûôª£.¦ ­¶ Ê>…O.à>ƒ5Ø Ãˆ“fd”éèÖÀ–­”tXàPVÄé&¶A¥`-¡R…"‹ƒÜY/Z§K¥)ú¯iª- ôØ(–=æÍ1˜FN¯7@þ®&±Èˆ³îøƒÍ]NAûj‰oœŽE,{V¶™*³¨£ÿBÉc´Ô‹ ¡FÌ% [5¢.á’dùßßènø88t·¶äµ9 ……ËU{¶ÿ©9¦YÀo.fká›7káPsbmî<­ù^PQ⚸JÑ <×xПP$©Ù”RÁb;Ãç„ï`yK^µHõйxÆ‹íO©ª± “Bj:ÜáxS  IÙFB¦ªT9ðD—º¹ÎÐ œ«…·>µgRsk°Ý*Ý ]6þÑGªÉ@o4 ¢‚Ê¢§~¤ ¶OuR±Ë ”Ö 0=ªoðÂ7C”ÚdÑ‚»qÈ‘Þí3Ruó¬ö$>é^óü[BZ¡×ì§„9–ÁVK刋'Ì@tÔ»4ºèêºÞ¥áÏ*—ÔЍr# ez¨VuuÇ×ÖR…TƒóÐ4“óÒð@Ž^OƒW.ô¸QB] Œ¡†Ma±$P€„ŠâêÒx½žÿÅSáé g¼^µeS}nä¡À†³jÁ+Ý%Ž%áÿ@nì·Æ7+G+ùj^ÅVKå8‘P Êu¤K ›ªk t­„Ú£µšß£+¡¥ìšúÇÓpìÓGii©87ú0pc%ìÍ€'*w˜êöÆ…ãdÁ\!W”½j±\ìÍHKAÓu)i ^¿×¯¶ûƒµSp§A*R1;mȳºÄâº[¬–oRþégóD½¼öYm“ßrÈ3îr-VéŒY¨ªÕñWDÆ‘.m³„޲Pr¼ó]Õ«öq¼‡CªRÕB jó¦ÔžÝ"J TÙ e~§xšÔmÕÑ_m›žzÆãUs5RS´Ã¤BòÈñêƈ”"MXvãÏCTqöŒ -mD'Çnâÿù4Ç}òÍ¼Š­•Êq¢ÎU[ÓúßÚjœ‹½þ4øÍôF ³C£”Àug"o8ëßYrd[dÕ‡zܸ/U#Ý-á¯Èq­–FÇ5lÓ<Þ_ _EŠZ{Jëüš’êöxtjèâµ»ŸŽo¦8ÎnÅE'‹–Èv­/€¨×ëpVÈFä‰%‚cœÜÖ«Â\årÄ1F¾‚W1¿•R9"uÎvƒ¸©ú¸cI?m­ÑS\û§ëïîåØƒ'éÚØ¯Žç˜(­J‰ë É(_ªú~Jж%nâ¡w»uZÿðXËñÔÔ}Çî)IMÕLUÒvk•šŠ4†ˆK¹oÌpÜݤ òáiÅ«¦*Roœà îºMÊq¢¶² ªíjÇI㱬7!Ž%†*—ëj v-¼Ž…RS¹µÑ—™—Ô<ŽUÀw<¦¥áX)Õ™å,AÓýÔ#¥¦ªô >‡bÌæì= ¦šõħ,fŽK¸ùFôq,ñÓéHUõwÀءϖ0ÇKò¥,ƒ­”ÊqðÌÅÀA¶µØXˆù6ñ,± \wþoÈAâÂ+×ï€\©}—’U-3hÐþ÷zö©ª‚PG|HÛe9˜ªfçaËxÛ1¸V>D§Ûæ>– r`6H×Q;Þf1›Í½ 6v!⸅]êZu˜@¨KÛ¶Kã~”xo'ÎÜwÂý2UEðz([§+ǃ#Õ555±JéõPtü„±jÉš£µõÕííGêk¡Ú@‰ä7œuï>Þk "½Qbù_·ió†Nã ‹ë¤¨ÜøÄé€<¶¥€ÖbOÙöK4ô£&°xSpfÓÒÒÝ9FÞf¶”ˆÙ”`íö°AêÖGÁ8š$ic¶âXR7GG:¾ëˆï%/#‚ƒ`ylT޵X0ÔÜPµEg \Ñùnd¨ª½Ú…$`ÇŽöÄ?®wŠÈ]À†ªÞlø”¼ÛP¼-'5%…ÒØ(ª² n&zPª·—ªò¨r}¯€v»9Zpû…ÆÑi°Ÿ2ar†2¹æXŠ1rvkiIP–`ùý¨ö1”¢ ›äúM'‚ãݹø«O?Û$Ui¬O"gôçÀÖIå8®eîÅ’Ãm5uµy#Þ¸©W©NœXãz§òêê°&¢¡ˆqÓå¦iJ?­Ä=ýKQX×ã¦q¹ÈÞ\^] ý~þCAð^¼(«E§vXÏÈáAfc…SŒÕlÖ‚~•¨vHÜÝCžÜè'uIyC©6²xÚR†Zmô^O¡î"×Èý°UR9úãzøýE ° n\½ù†’#°ÔŒ…¼åÎwzÄ•&‚ò R%FMÓœÛëfi¿(—D‘éh¡·^‘$T×VA5Õ!u eCƒÓèk§¬É/B*P&-VÈQ–÷óÅOüÆóœ±´ô‰øÐò{ôqÓwQ{'É‚ª¥H ¥ý¬Þ^"8Èãå=ª÷ݺá[ÅJ7¶‘ºöØ*©qeª\mE:x3ÚHd\ª¤0q>|'â0›±Øc6Pn7CùÝ@Z¸§‡-õв$Y,Q‘ýjY\×íiÔÒàs nŸ^ï÷7ƒ ìÞȇÑaJ,¿KÏ'y–¿`äüÜN{)Š÷íV ÁE›šDLS½Ø\uw£oé[²§ Á›\_ÿω­Ò•ãd|æÞõ7¦‹eYªÚëÖ ¡“ˆR¶ÝF{Üýz¿„ ¤C£SÏŒ$Í4h}8¼ Ð>åvÓzÚ§ü§åyyÑ«{ÅŠ‘‰¿rB£à8¡´´TRUHÕ¿±¹Nï=ÝV+ÓIð¦¸F į=¶F*Ç||}ã5w`®›WÛjªÅÕW¡3ÔG"%6ÜG¬FƒÇCQïözÝ´'Áà"}×;þ¹h·Çï÷Ón^p  vTh½—à XÓk· ÍMÂ)n§±ÔäFD󆫖‡M5¶nO¼v?"w3ŠÄn¸ÄXµöØ©¡Ää½ê·1R-Z¨յκXÏÙ:ç±·†ÿƒØ$‚4s[9o-bÜ Tíöz8èÏ9ìñžæÜ~¿ÁMÓ€4ü^_ïwûÝgùAÌõ¥8$øn£,ß.±Ó‚ÑÏqF®Ô\ŠÜî–Rôq-¥ˆ¦Î6oÚK—”7$Ð0•˜RA¾‘5ÇÖèÊñ nI¹.-ðmÔÆÍêZÀ‰äo›É!Õ[zà×.ß³#¶l±ŠV†á(OGÃ4ÞŽÇ)èBàýgÏ6ƒWcxÄ0­@‹¯ÇQT¶’I¤5šöØöx=Ç2FX—ÊbîÅZ¤[ÃzK&á_*5z¥ÍâAVvò†Ë–49D´×["•ãÁ’….˜À÷v¢#Ð¥6šM¬tØÿ¶ÊßzQ”"Î ;•Xzƒ¡h÷¯hšöxÝ~Õø¤ÍÊŠåÀ; Ãð8&JD9ƒÏ1qÈÊ<ÚØn7|ÈÓÍsÆÌl³¹4–±!ÅÛǰdsœÓñ%‚c‚ÜËø–ùjIå”Cä;Y{l‰TŽÎW†üÚꎛoC7jŽ…%Ù—iµ)€‹ãùB6Ã1 Ô/¡­Ê-ÄÒ8´mÀÃ3Ѹ”½86@ažçõF0 XÃü{±ÃãDŠ„£ZÉ6Å9%õÔ –Ç+­p¿"ßÉÏ€­Êq2a¸‡p¬¦ºc¥èªåWwvT×Ô&4ôXuX•¤úq'zÀ(n¶âàè_éYÊ “4ü’hÃᵃÂ4R%aî” ;ŠK˜8ÌÈþ$º›€¬øÛ¨$6ú4ÇÑ~†5›­Ö‹M„‚#d)IøÔË-H=Ñ[ÞéMpJ'HyC‚eñô3Ò÷}`+¤r¥ÿµ5­×ß.‹C‹Ã½ÙÙ^]㬋‘‡s5Ä¡(õæÄE6˜ÌaÄAS´›¢h˜#îiÎ6ãD‹†3ƒÈ:u›/FËÍØ-R_„ë£4¯Œ4y›8AøæmpVsö$8¾¥k‘’Ç þ ÅŒ«c÷n|›¯òKÊ’™a¾‚Ž÷‚¥©›”8œ®£3ª;:o¼¥{#°4´ °G­;&õÔ×/Õåe}É÷·½âàøtŠ\?6*‡rüÈÕ›Zm®µÎ¿ÙQsrW%Ú£w7(²d5rLt3Åpœ›vŸñI Zö ¶“½øÂ3ÐÏ1/KQ–mššŸ ÿP`sŒFŽ/jÊ]߶૵ˆ[;¶oß±ΗÖS'å ß#¤CÉkš[¦o:!ŽŸ ‰©©›0•ã˜ó2R©‚c-4ÇÎö#5µÎ“«½*½4­ßÉ00 ª@~žv7àæ´ßC‰V ¶R]¤eñô 1ƒýexÞÇ Å|NN£Qý¼Õb Úm˜'ŠŠVVÝê‰NïÙð·Ò’ò†GʾG´ÆGº¾L®k雥¼A²Æß›ìß„©È9Ž]7qãñÉ#ðÚb‡ÿŠ8Ãérý®Îå\µ©jAÄ‚@‚.tsPœü ´§Á{æîœ‘PତD'ÄÓ§OQF= Pœc<•SlÜ­o„¯¿ØZRZ2C–×q˜X|Jÿxƒ3©§¾Ž˜Jâ%iIêß§Uä|ý|Øü©ÇãqkjÚ®^ÿ×ÀÛ&"™Ñõçë0(7“ë\m•C5{üˆ Úªðœ@־ݿ>mjn:{ž1°vžå3X.‡çšø6‡âŸ‹ž²•Ø~\ç,ø“þñƒíîš:–D¦’ò†ïK½÷“èR[ò·UÇøÏ‰MŸÊq2!mÜårÖÖ´^½ùÆ2áfgGu}­Så gÝ»—Ç•¬fðšÉ Î@ÌAùýLÍéÎ44œñx=×39¼‘çy†gšyéb»»ÁCíf³eX–Ä`i d ±Þ‚lŒ45¾~°MßÈç“ÔSOKBò¸È•ûKþ´§ät½Ç¸Íw¾š9î:zôè‘·ŠÉíl­u½RsäšÇJ%ðrË€]ãcÔ‘ÁÐzN`õú&½þïiÀŸÃï4ú…‹ß~;8:hÿ²Èb¶Ym%†@fzÄE‹¯7„ãu—M _<—²}ä=K’ÿˆàxŸ¨|ÅÿüUÒhŽ)âàxŸØô©Ë”qÕUß¼iú†jªê¬q.-9z§¿K‚7ÎgsG!ï8«€¸ÐÓF–¦¸F£Ñï?{ñâÝA£½pE©Õ _KK±v±²’˜—¡,o°ZR„|#;:–(ä wðzšª‹æÈ¼kêñ&æ%¬º”ôIÔS_W¼ZÒãÓO÷%‹‹¼%î*# œÞó­¸éR9B1¯ôk»ŽÕT__]` ³6¡TÕ±µH–‚6‹z›Á0™,cØi0egfgg›-Ù¶ÒÉ ‚м¨4¤…wx8@ÒrcV”%å ×Ê¡W‰£¬,I¼ sq¬6FÎÕÏ~-lò®Šª8\Κöö” ¸ÚŽŽÇɵøëDY2¬V›Õj(2€;›Yb(**±˜¡YÊlî•÷`¾ØSríž@‘º·Ì{ÞâSn¿JºÔèèXZÞpœÜ¿ï/÷}º u$ÉãýýÏo¼GlöT5÷Nâx—ª#Ö£qÌqbmþ<‘û›h…åœÂ™0k}ô†ÕBÄa¶ÌÀ­lœY0׿E‘Û—aŽèèØ½$5¦–!ޤq(LBÔQFxã}`³§r ︳cµÅ ãö tÕ¬A;ŽK6>ED±wOýžH‰¹Pø-ÁÝûàÛ½Ú6h:/ji o÷¤ž²sì˜ßh—j ©§¾îš£bæH–¨\åéý¶k¤’ú{Â&Oå@MÇ;Ëä¿ •nÆUÔ tÆ<íκ5©3‹sÈ%YTDµËÒ$¬ˆAŪbåñGEÔ%åÕ:ˆ¯E÷²Ì±Ñ2:>"å ×Rë«Ä‘<±¯ä’xØä©ʱººú€–ÿ½ÈWo¾qt:kª;5Ž ´«iãuuÇ×àO[(­ÇS ªg8iéÅb‚?‡îÐÑ3Mè>èE½Êåïšç µDzÍÿþö5‘¾s¤ôl¤3¹´¼!©§¾>7T_ÒÚªq¼×û1us§rœtÖuÂò†7n^¿kÛþk p½¾ýÍlWjØÌ£]¥”ër½[Þx ¥¸ m}ID圿×xÝ"ÅD‚¶’$AÀ*ÅR¿Ššß¡Û莎%Þ¸t2H¬Óà¼4IûÓ ò¥lElòTŽuµ]0ƒ¯ÖétÖÖ·_íì¼ÚVãªYâôX¹òa­«Îå¬ÅÆ.À8Gjëœ5ÿR¿Y¨86Ji¬Û0-±H©YáRpUa•—eŽ“Ñ!m#å “SK ¶ߊØä©J ‰Ö:n‹&@CÜ ¼q®Ó w9Ò¥RG×õ›Î“kó·I*?ˆÿ?{oÕÆyç ÏŒ>Éú¼NÜS‹cV41ÁVÜÝ}ïIJÚM[ÇuÚ¯³7 œ\´±“”xóÚ>÷æl0ÄÙ³·ÝlÜ÷^ ±ŒMlã66`̇ Ž ï)ömë³Û¤Ý7 ìm !ñëåÃF¿Ï33’fF3B ÍŒþ?BHhæ?ÏïùýþÂ;Â2ˆ` Ì"@˜V˜^¦Cr‰J3’¢b3è©§1J±I2ø‰®§Î[9v=}þø ’þñã]ñWX=Í&6vn¹È;)]]¶.¾¦Š_÷îüÇß²Ÿõçl!ÕìŸÿGÑ£Þ`§„ñ£$$ã¯Ë':´‘+€¼¡šÖ ¡lÕp82:oåøß[Îo•ðÆ–.™rÛ(•þ«Þ­,íìÜÚη‚ßúbÒ.•Ù¿`3·+þ‚ó:þã7C^ûÿ÷ÿíCöQÿ×_¢8'ñ«}Ú«ÝDè©« GAß#ã¡÷VŽ¿yNªl TuuSÔ;¼fŽö¼8â––ãx*Çó;w%ÿ¼Û3IÎ@q¥D‡ú™ƒ‘41Áe›ær…OtlóŒ…Þ[9DB‡;vì UTu?ºõ‚bn#Ü4ØõÜÎnÉóÏoÛ¹sÇŽÙ¥~ ·ƒI éË&:§ê o¨> oÚ´ñÉ+Te,ô>•ão¤ª.®±£}K8hö;:ÂÔ±¥â"×&Øu!¤o¸cñ3œÒ ùŽÕKWÑ o¨ Ò©z+XùB¨:×Å»ÏïÜù\W”ÚHˆJ޽°õ8O1í¢'xqVËC“ŸHþÚ ¸h€´CïS9v hƒ T±îú¶¯” å:ºÎE:<¶¾°s Ÿéxzg’fÿ©ìè`@OPô>•cöEIEU×y¶³cçÖPKø‘öPjc[8ò4nýkáƒUÞxIó‡cˆ’+ËUqG‡TOä 5@ÒÊ1§·÷‡îˆªºŽsY‹Hš| ÿUWË ¡æÀŽ–È?Ù¹å.Ám‰ÇÚ? ÒUCj W•HrùpÁj€Þ§r`Å*ØŸè:ÿÛHþü [ÃZ†/<Ç7†oÙ¹ó¹óÁ‘ v:ºº*B¼±K‡C[‰ŽiÉŸ9, 轕#8ûVy~ÛDÇ·ðä/„È{Ÿ{áiþKäŠðñ+ž8žGNG{W×Å­Iœü—~($:¼ªLtHå ázÔµr0ƒ^“Ó+Ý<ÿ†óÚ».<ý|¨-#TRÕu<”íè:·#À ‰¨ãÖ¿çÎóÁ*mWT !ßÑ¡ÆD‡TO½.W@ÐO+ÇŸùuJÆ]å(áøÖ0l eÆÏm é²^ÆÎŠ®Pr<’RoïªÀß~¡ŸS>a¢´ÑÑ!‘74¼! Ì‘úˆL\§OÄ?ÚµcÛ–‹[°>îN{ê"ŸÖhÇ\ÂWé ×Zát‡ kðùç.¢‡]ÕÓ9g´‘èÊ‚ž: 袕#Pk¶è®Ïm¨´sÍ}ç[ðp¦ç_8Â~‡ÉbWyuq‡¤áü¹WõuΕ::ÔUT'•7¼ + è •ƒùÈ{oºk‡Xx„køÛùžÛÄsJWûNqùÎü ƒHt€¼! ^h~*ÇtITPP™9øÐ; –+wÈi‹ì<Ö)ªŠàªϺúgt Jþ´›p©êÆ[9CÑK`t£Ø.a¤Š-§ÝjÝÀeºc'ª{U—§]©£C­G \©€Š íVŽq¹E2)þ«Ï8ºÎíxXÑŸö‡ët»Î‰U/}¡Óó®ré*ÐST -·r ÈÆêå5_¼È3G;↠!7<×.¤ÂeUáov`®ù›YýžxÙŽµHW¼!@Ñ[~rÓ»Ggà@¤Úmå¸î”Ý4²‘zfW8ÇqA8Rö…-áöp\b…GĆˆãy†©B—®R…²‡TÞð:\§ÚÀØÑ'7¼;œÒ©²'øÙµïCcOzù[ÒÊ¡•É^ùX‹r§ØX+wgE—8…û:Âs7vàñ]ïóÄñÒ¯u~ê§UÛÑáÕ»Ž>8ÂÏ•IÝk ‡§ž? \M©FZ9ü&Þ w§Ì.,\ÕÎꋘ£="2²cG ö9ŽmÙ±cÇ‹W3àÜ«4Ñ1N‚¼¡÷ ›Â‹ú‰T½ÆÈú†á§u Ö`8yÚK(ñF¬ýì.\ÛµUXq»³E,2rü|W×Åç^Ü5›'_‰Ž"ÐS×"Ž.Á¢þžà5ÖÏÁ1O#íMåè§”xc>×Ù];¶^lǶ§»„9;·nÝúR†ÐF':HÕÍèÊÁEª ‡cƒpQK‰Í _býtÅ+TßÊ1N+ц)Ž˜Æì®—*X­õPÀj‹8tµcçKW™ :û*LtHõÔ¸Fµ°-êRRö4"z ˆU¥w!ÖT&’QJŠDQœ±ùßìziÇŽ!ºØz|G8űãù—®ÎfÚùWÛŒ7Ô&Þ-êë7¥¢êILNGá §ZjåPªÁTíw\ÝõÒ‹ÑA-AG‡x£`†³¬E6¥.CmPÛ迲0÷§J)W™ÀáЦ6¤nm2uÞ `aû{ÁnJ¿’µòpXr’â©¶Eéªz/ÈêcR6pÊqU ˆ„ÒßU·ðá°€¤`Hö˜Rœèè׈¼? 6&Ÿà““IßÓl„‡ê˜Ã™æ >aŒƒ0UÚ‘éªiÁ‹:oÃIЮÏ:|dÃG†S°z3pœU‚b¯“ò¥;%(¦;°x¤eFÇx¸¢‹„D –7³iŽ@ÆñISE( ¼kL‡t•ŸÝ4P„ ê­ò˜AÞ̦”x3íBy8, 1牢lGGª¥«†h§“U7R88ä;0É!82@-P¦¢`8lZÀ¨h9D/Rýf¨ÁUÝIQHtÌÁ¡éGŒá°(M#䥫LPôÒXÃaá褗ÑÓ&]1à‡\õB©£Ž `1`îbn ‡ˆÃ€î”€0•ªO †‘ôÇL ¸}ëÖmôqëÖ—·¾D¸o®ýîÂ7Æo|9X„‘g€&7É ‡U= ÑHn±!ÄÈW7þe‘Ƈ^ûøcÌ,°·ÿ‚hä&ÈìqDc8ìl3Ô骄Êຠâà¨âÆÈ¿\ áã9 ²xøpÝ~~XÇþàãA–GîÀÑÓ”C9Õth­Ça4b5Êhtä¬=Ô4™¤5 `8¬ ¯sŸˆ¢Ôá}3²9<ÈŒ¦`ky¿ÆqfŽxÊà0ôáþý‡ùû›}>\‹¾®½vv0¼Ïc$%q Š@÷=û’ ‡†Ãjf!›èpÆáF0ØŒrl²§›GÆQGsÌ1ˆ¸"|Ç þ)Æþºýâ¯êïÿ¬D3˜kòˆX0zš¥}­\ƒKÀpXõA6Ñ1ÿ˜¾@“Ç8ŸÁÙÎ8æøû,e°>Gè3 r/0W`îàøãð~PŽÐ š<1?(OÓ‚_†Ãj ¦hê˜g<=Ó´.ÕfЦ¯]¼6xÍï÷syŽ‘k¿ÿddâ÷ã}Ø(¤àÃ÷ÔÞ_[ ÇLa‰[û¢TгÆ}ÓÌüÈXÃaá ¨ôzîè0Å|ü>'(ã¾8À™‚Àñ7'oŒß˜B7ã7Æoâÿ7Æ¿¿9~ãÆïíúµë¿Ç|ò‡‘k×®ÿ˵‘ßÃ1ÓmL­—5¸”‡aíTÂ/Ãa5‰â¸‰ƒ N­¥ˆD@­b ’KÌ“¸Mã&æŽqDˆ/nâ/oÜürüË‘?ü~ä÷×}`þ@ÿÐ µŸÑ`ðÖZ"q¬½•Ð%ÃaµjÒDG‰Òy_ Á!ΰ.ÆfŽ›S7ñ7ã7qœêúµìiüë5?¿by9 ÿ‡Lå sˆ"ã¡Dö­$ ‡Õ*$ aÅÀ>ãB¬ˆ2ìƒ#œ øã?ûlÿþ—.]úàƒöí[·®ábGÇkñZGgçɆ†F£Ñä ó×å{½Þ’¦¦¦8dêÆ°ƒX(ÃqÖÆÀpXMcÆ-tçd½Ö᜛QÎ0bÝc®£££ Ú::Þ|åk_»÷«Ë •m˜8*q´W6T#µÿDeCÔN¨k‰E€ZÏK(‡…ù@ñJ#‰J®ü-°83"ÖÂ!Ö;þÑb‰“˜8îEı¬²{ÔIÄ%mˆ)„XÄ#pÌTŒǽ}Û¶mOwv¶½ùÊ+\¨ª³iǶm;Šˆ£½¡¡Á#°†Þ„ZmÍ…©(ÂSy²ù–<ÚNV8g˜€\ÝSŠr˜ ›oEè¶ó‚Í ùØÆÖ!ÞØ~˜%.Çqpë6Œ#¬ÇÑ L’ ï!û©R< ž@AC˜18tžg?U*t–{”ÇQO˜a8læšQ‡,”Ì(THtÚ¸ó2ËÛ¶#£“#ÛŸïäî;‚¶˜8 „ÄQÙÐð£—Á Ôˆ)‡¼DcG TæÈ•Õ:n(-*&¥á°·áèÛŒ¤›Î#3’MtLÁáÔ'vmãñ£vä¾ù曕oÞÃ: Û¶mmGTÒy±¡²!"“G±GÅö]…ø¥ê0)§]êx­­cœ,Ù/:&å_djpu¾—‘3#cef)òLÁ¯_ ÑÆ‘6Dˆ::›¸(Õö­MøžÎÎ3ˆ88Tr•• ϼ°mÛO¾€A€ê‚Üolèìì˜m…ÑÔa¼!{~½0V߸á3#ìṪóm2;#0‡î0ËG©Ø0UKm[8o§Ê9\âI#D‡ØßÚ5 ‡PEÅIG´ä\CG¼8™g˜ÁÃa3-NeKÄŒ¢âž6ˆVél¥¹¢Š6®Ü¦­ãðvöŽíÙ;0•tð~†€7òƒx•zˆ¾à :;âGgCT…®C.“å„á°zÆíèíGaG"ˆÓŒšR½ÈóÆËmìú‚HâøÖí¬¿“!âè¸( ŒÊÐMÈWùÉp U‚¨B˜œÎŽÄÐõžxBU0Vßft!A3j‹6#ØXè/JõòÿÃe·maýˆ~ÔÔ¨¤«³£­ G¯Î4"ªhl¡ò߃ÿû'Üïï €U¨ÒÂ{êµù/ñ¨%¡A¢^}r`8¬Ž£R3"_KtûÑq¾3ÚŒàÈê»^à=†_ƒlÃ8ý4Ï$—;Ú.¶µA·?h»tæÒ¥û>8ðæ¾@·¼‰>°nò.–g¶½ñ*@Úèk<Ù±tž4Îßüë„á°`Fód:¢Ì­ö7‘(Õ‹¿Æßÿñ¡q4Pqæ”oþÝî(ìa‹üï„ãU@iƵņ©xâèl“†¸£kb>á°:E`$ÊŒfGR3¢®ÁÑÕŒäÉ 'qû™ƒ3ÒËsçe6)¾Ra©ª/~Â{ [Ïœ’'Izƒ½Ù³+\ˆËð“í/C? *UžŽE¢Ó_”v ºÂmñ†ÁÓµH+ê4B°J'uüEº/˜PàJ&+Îãôn9üjÛ¶—ùXÃ=Éöma:,)%—7::Û¨¸Jbàlë ûÄyñE›Q‡ÄŒÂ!Ö¢³Á¿àƒJÛ \ñwmÛÑv0~yI†8¢¢Uýlb#äcÜùÉvÖ•yé*é%Ç-ÑõéèHNŠc_·à ë3ÆØftaÅÝ"32ÌÁAÖ"s„|‹ˆ[•WEÂTxtì×_m1‡LŠc÷žý\=o˜ƒ~û´’§ ¢”&u2Ä' Cb3£–©u%ÃŽ*ÁŒ´ŽP*C°°_ E©¶U„Iã¸÷Þ}JÄ!(«úV wòDt•÷^^ž…j›¥Ä”èâlèHÄi“)ˆJé8RcF9`FÚ6‹Ð°¦H()ÔÍñ‚0+ŽxÇWEùŽŸÉå8þ+Ž|½ÀG sõ%þE€9Òåp$é‚ïh3Â^ÌhñfdžGÀ füöÅ3f’Pž|Ûö]ŽÀÄñµ{¿úÕ¯ŸÜùª\=îîÛLð‹—9ÜHûïu¼Ô±d˜f8ŒmɺâÅQÈrè}g9#2£Î™Ñ i !p•‹*½ô… p◙Ͻ~¯p¡*Û2a°ê§âà¾ûgöy8·çÄC+Îëø dÉ—¢Z˜“Ƀ(Xµt&™Q\ÄW¶¼ÌH»à¼‹À"ãb_úu0øK¡Ãñ ©Z¶Ìv&âsü4Bù8ñzMu„½P Ž%…QYidqhM„‚­oØ’[‰ f¤ žØþ“Y&ÔuñEhÇ6V'DP}û çq|õ«_]¶î´H¬*º$7$:J–üä·<1ÂÔñi¦MdUª¬ŽU ›àPƒ-¢é`FÚ"Ž##³/‡Š©øŒGÄãxók\rñƲühâã§‘ççV/¼x•mÁuõEž: I,Åð$µõOD,'çé3J°'ÌHC\ÝÅDIÛ¸V½H]î™pׯÞWøªªeË–Ùç#Ž=BJཎíWïpN ò:~ÄÏûæH%*Eލ,œG=/Ä™M€šD¸S\ f{Z©â=Ùyˆc÷ Ñ3ÏîÚÎêŽìºZÒB^PG*C Éî…§I2dÆö#µf±*#<ˆcÛ.a˜8Þ yˆ7ÄqZ8þIúì|kúË_ðñ)†ï0„,ùÒ„ê“}Åw!Èf”3¢ÀŒ´ŒPëÆv‘,ÈÜiA1®Àãäž>#O¤ÛÌÛ±ØáË¿{øŽ]pèSÁÒNu&ùzïì @AL†˜Q[Ò‰ÌHËøâ¥4®xæÒ—§¢‰cÙWã Žˆöx® ê…m/ñò‡Lyàq¤ éªÅ妶Á<…Ì3#KèKbzÌH‹QªíBÑ?F4©^ —ã =]õÝ{ü>²‹ìÄ캀,Gа/¶¼Ð…dæ5÷ÁYÔ)˜})P©R0# z5¥Šž˜iß»7©ZfXiïP Ž/ÌÏÀoôqÀ’“"äÒ׋ 1Èî* É‘ ¦8.¤€8 …IX ´¥úI8'uÖŠ#¯ìå=\Žk𞎩«Žð;ż³‹-ÆÝþâ. ˜RSX # 2@tÌh:$ `FšDxô`àRTQªòž‰-QŽXáLùË0Ÿ#u!†©TšŠòãÂ6‘)8âúD\ft~)´ `FZ%Ží¬,•~Ù& A9îÊ3§O‡åqã*«’`v׋ˆ9^Øþ“;pR„a™Ø4A¼v¾#òµÜu¬pwGgCƒ"ŒŽÊ6¹y  :¢W4É›QÄ^ÈÍèµ£ø‡`Fš%ÅÒ¦3¢ªW8UuLÎHGy´ÇÁ2ÉÏæÝ#·c{üUUÓ^ƒÉÛ?-sÄöÉôûŠ®x¢2‘+>œ01ž”Ir‡àJÒ'ÍkF Gç_¼&Ÿ+#!;®!âØ¥œ‘:-!Ž{yCÐ:þÓ½2#æÒÀà®À8‰c ΉèC&¾Ó!ÅŸ—½â+Ï˯9•'ÛNVR»˜æ¤ÿˆ§ÐŒ:"ŽóòÄA9ÿŽÀŒH0#ÍàåX)ê0=ü÷WÂãÈçÇ›{努¤¢# VÉ\ýM\¤Ÿrˆ ÊuÌÕ0ÆŽ„®xÙ€u¢:I…CÝ‚+žÊ+IŸ˜×Œ U5âq¢žoˆÙ;>Ý¡†% \ L'ËhæNI’㡇¡/â‹ì‰OtD:â{øóúQÍ!S ³ˆƒ@ ;´€[°Äê©0#Ñ/Ä4#5¤9p± Dúÿ^ÝêÿÃãÁ(§7å&âìxK²‡àœÎ‹~éA3Ê4üÊ\ñá»^ã"ZØã¨tP„ã5…J™B‚ŠÊ{@!¥n!5£óB›éŒ6£J¡‘$2£óó± ¥lFjXRþäÍg‚P•`ŽNŽŸŠ"Ž¿“ˆŽ$‘8h8¥ó‚ŽqÅ$F|tÂ!Ïâ0qdqD…§:;ã0£¶ùˆCÙŒ Ô°Ô>GrVíȧW÷ŠªªhDZ{lŽcOÌg±**Ö/"Žóâ+ÞHxN¶ô( íé4†î¦`‰Õ'(ùýGk±ˆ#†I2çÊf¡†¥ÆPRŒæÒ)YqÜe†•mc¸ž*ôí—@i%BöŠ/ÄW{Ût£xÅ㇟ïì, ˆ“²j¦ rjU°Äê‚SüšÐŒÚ:9ÌcF¸Øödü¡•.„3ÒPU(9.˜{f·|v|ïï T•Ö3.øYaäŠ%D Áyî"î_ñœ?AQÄqþ$Eätqd$qt)˜‘¬ãŠ%Ø‘ý´QÑ ç’J,e3rBŠ#íE6 ÁiQUU8TeXæ 7ŽŸ:#—Çx ’ãiõ1å=¹+^’ÕÌ wôFg9N cG†Gft>L¼’§'ÚŒ$G â€ëZ‹Ç­S§¢²ÌQ!ŽÓ»e\tÏÞäXB˜â N+&Ç CW<»±ìeÁCI 6‚:&‚Gææ8¤ùŠèPUA¸˜ÂÜ7qPjÐ>q0ÿ!Ž=ac&Ž¢Ó§¤ò¸Qìñß’h½P•7dêè„u”Ê’#2å0¢;ÄÑæ ¨†¸Êa:¬ªòÄhŒ2#JÑŒ¨ªê§àÒÖ`rüŸ#ìpòÀ¿}õÕ¯ó¡*Cíi¡®ºB à­$š/ôÅŽLNļâsó8ƒŒâpdC—¼<.‡þ‰caf„×®…öq@9®&S‘¢*m§Ïœ9óÁ™>Ø·¯Ip·‚®úîÝÿo2í””£TòZ‚@,É’¿ WQñÁiÙGgGgŽX±:Ç3q›QaX²PlFžè1‚ß?[€B é\, Ruû³SQxrÄ!ò=~ã¼Ò ÈC.Ô–|9L¤ªJ”àðˆ7Šç…“œ` N!=ˆVʘÑùPÂ"TUÅÞ![U1¤Nñ@°¨€jHsàb!!üt¯‚Çñ\uéÄ:Eœ”jED¾öP[&UI ð;ÏGõqx¸èW½­ŽËÀ6A—X+1#…ý‡‡à͈ˆ˜Q'ïÈÆîãèŒ-² ¡†¥ \Ì—’8^U"ŽpÕ¥Ây ÊW|E8;OÆè–ßNñÔ„¾ç«“küÀáì0`G†›A8NʘQÄŒ:Nžl@.FÁIô9’=æqèâPÕIœåõ8ªªvÿÂtB8°0†¬)ÒʉTUqamR\~/ÜœD|ppF·éQ1̈Ç”NŽòA.Ç…;Š6#bޏ "Ž7ß|󕯽òêkgÎHˆãMÙúögpÓÑÌq‡88}^ÜùÇiáFˆ£³Q‡£²³câÜ0,Z§˜šQgH¹0:ìTé )¡vÚ(JjFÑÄÑ)T™„®âtŽó-€_;#ÌŸ~SÄBö€#˜V/È ±glttv$aRªq3ÁŒ¨ṳ̀³3¾ûÀŒ2ÅãøÙ^aëøºä<Ž”6rFŽLt:™¨%«þ5NsÄÖqÜ€C˜Nì“S‹ÔÏ/òâï̤fF˜ÃŒ’0#Ý!2ðÔß»7<É ËãºOŠR9ŒJÿa:!ÌŽ“Á ûƉkp¸3ÂŒ:SkF×q¼*U!âXFŸäXÔØq@ª Lr4t%ûŠ–ßà`ëŒA¦“#¶ûºP32B3Þˆco8TÅ©U ˆãÌ%™ChäH/„ÑiÏ‚¯ì q„ 61fÔ™L’<ÁŒtHâP•3B§s@éÝ*6 KhÛ’ìp‹aèâÐ1ÄftÌ0¾{œÃÁ1‡óŒqühäH/æ(™ÀdA¸¥  cÀŒ‹ò8¾¸Ì`;ÓvZ2Cfz,BõŒ)Ü)Bˆ!s̨Ì0O¨ã³èǽü$'Û™S§çÕU‡F5*“Z #Ú)BˆAßf$Ô6¯ìL•Ñ*½Ç¥H"CÐ8ÎzË.ItÕ÷@#‡ áˆg¯xa±;Eè÷Õ9l“}!U˜‘^ xÜ À3J€{ ‘C=Ô/lÞJj÷–°Ú¶ô±ï3Ä‚ rJàq°¡*⃈R€ÿÙ®ôbF˜×¤N&­·Aô¼3p 3ÉŒÚ,ɽWAA`['˜(îy_åBUʇÀõØóÄ4»Â1ïì'ýEÙwaÜ}÷]ý¿J-É®¬ÕÞC- ˜˜‘–ˆƒ'æÔ9‘jÜÓ¡€8ÇÁVUQ‚Ç/û8ÄÄï|4ÀsGwg‰‰c¦µùÙæææ–ÖÖѱ1؃$·s’Ÿ~Mô|8™G’ͨÌ(=¡*}¤(ãâ†Ó§OŸÙýJXã0*TuF1DZû6ïD Ïwü%ô]d-Ͼçá‡+>9’7XŽ(dt ²f‹Š2 ‹®Pr±—|¥èé`‚B¦@lFX¦¿ÌH+˜zV ©Èx Â&ÇCã8 Tcqì‘©­ºÉ>Ñçþ oÖÝw±·YÙ÷¬x˜Ã7?F¦ZÞËÂZ ÖIÜ,J.xHifÌDlFTâˆÌhi1×ÌÆ§–ÆñóÆ©¶7¥’#”`vì)eãwレȺ›K…8£„c Ž8žùh~JHS ä@¾äÅQâ䂚µäâTaÈÜ`q²3YáN€‹:åÔÑÚ\öì’8ÌiaÅ­˜7 Ä!Aw ¢<îîucÜ…] ÄU<,ůâ'ŽP%ò(ĮÑŸ£³Ó#y¦8¸™ƒk„ÔçX°B&%5# Ž¥ÀXK îÀ¥VIy•€À§%DZñl_\Äñ›ÏBœQôðÃ|ó»Iy£Häq´>Ê€=Â!Éz¿@¡ÜNÉž“8‡6“Íè50#9É)µ’‡Ðã0ìø#?Û«DË¥À•ñ͘Éñ˜(oCˆ’4áYÀ<À®6#‘i0#¡u‚iÒñ(ÒYdÇ- q|MTU%ð8„9öˆ¾Ü{àᢇcâ»"â`ËqAÙqð<â‚4ÆäH¼‡¼€._3JØÝ3Rã!ë{,ºÇãße<~à2ŠT ©æÈÃóáWÒ—eæ¦ÆÆF[[[šËãôÞç8¤˜™=>”±î8±1uÅZHnž“n5Ñ? tfd“Ú-3:)cF ‘™^ÇãÙ¤—Z}yJ\Uõ5Á'ŠxCðÓŸ U¢LùÏ9üñ¼½©Qä€ÌÃåÇÁñˆ…[ôEOEegÇÉ×¢H‡ PP¥g0C´ÓI÷˘‘CÆŒâŠQ!3¢¤¿JÙFà¢U¹ã‘pÇ¿ÅJŽkO)Êã óCóÇGñÿA“ˆ>ÊÊÂ^†Ì»lCPÂhæ • Ù%â_„©ÀzÆ »Ä“„És,ÂŒ&àX«ÞñhN0fõG!5DR\ÿƒ8„œ8|3£8­£\QÜ‘@´ ‹Û¾¦X`umä~âT:w7Š"žeôÊ~CÆ$(ãkŠnGnW2#8ØZp<cŽPë­*¶¨Š(‰8j“èq–ÀÑXÙ1+y<ò—oAƒ4dÕ…oÚ<”ì/Hhê˜6¦iÁ©vÎ%`Fã }ÕÙÙÙPf¤mÇ£yáG$9ÎÍq"Ö 䨭 »?᪪¸í{¬U9m~| Ì@…„r ^k8y2>ÙÐðšÇ¡ôX…Âû~švÒCÀÙZ‡ß$:Ù2—ÞZy» ˆO¥ØŒ 6#@úBc£ ©äÇ«¯DFŽó%ô8ÎìQš;o=n…1R®¼¸C‡ˆø@’Ê?;k¹!à k’³í]ŒÅô‹«È×P¤ŒÄ‰# õ8mˆ9¼§âÑUß3q<ôÑb·¨s£ÇËÊÊZ Á$¤q,òzw\“{ډє@B»˜öJÏ·SnÃÆ,ÞŒ *O%m™¯ë:±PÕg¿½|QÕ½¼Ç/qüì‘X´qOvÖådÄ6Æ”XZÆ U.¹äƒkuÁËÇNáR3ÇY£˜pFp“ÂCiF°»P…£ÑW›upæúøg§eªªø‡˜8NËQÿ©¢H‘4–ß}×]w?’¤c03Ú"ÿ¦[:$^ønÑ1O|ƒ‚ƒ¬I É(½JþxQfÄ@2,ý¤¡ÜÙ Âh¼›Ò‰ oÖ]E'¢=Žp9® OIr(HäVÈö‹#W#‹›ÄÝôØñ2ÙîŽ1°R‘{Üg\ÐõNí *l¥WiÑ.ŠäNz¿ò/2,ÌŒ±NsÜa,MÀ8xƒA¤ÑÿÖ‡–ô¦èäxXãÇeaõÚ"‰”úC+²³ðócdû§“|DZä2åÍP +ñÏhX‹-tH”‰Ã·Eàs¬¸'+‹›{×]YECwÁäOp™“Y•C¢\|®§ÖR ^ï±ÔÀLÑeYýp5¿I¶ºnž Àd‚fD®mª´Æ§Ž—Ïjni‰Ûß`˜ÏûÙ¯idßsOEžñwZàqðÉñe\ ót\9v¿QôÐCX\ýžˆ£qW–w •Zòâì-PŸ+Þaî‹?HmÜÛ3ôÊü„«´„ù?µü­âž¶}·àH§ SxK]C+ÏÇCìÑxyãsäip^ïº;kùЇÆ ý7‰Gdä8_K8ÛD€ÊÄñPEEÑ=Ëùð~¢þñÔOŠœj•9BÍ£`="4yâÙ/Rž¦ùž¨_î÷ \¥M„WöÌ;ãÛÞðÄ“ì ='à@§+ÀP)Góq–2XÄÉýE|š:j*xÑÃŇšžDÄÐ Uñ¹qÂrFàqüô…jÜxcÿ=Yܳs9Á/–ì`ɤ;ÊšAÉJŒ@“Ç“<Œž¦8X~Z6ÎAA¹6 S…ËTÅ™€ 0h Ûï@fhÚ0Ö«è¶etLxŽÆ—ß`øðQÇ‚GM/úfÑ7‹÷5}ýÿ‹ã~UÎã)«úÙ«o¯È¾›OÝ}·÷ÿí%^GæF›eºa1mE‚Áá} W½Ñ³o8Þ¥Gž} \¥ ÉË$¨€ÌHÎñ 1#@ò¯p6­¡LR¹Ò¸ãTœ§±â‘ygõ=´Î½96$§  "È˼âþµùæ„âT‹ ŽŠ÷°yð»Ùæ¾ì¢!LjÏDOÕm «äª«4â&©  LʧÛGãYãåGÑC˳øâ)‹å®ìG§5“iž•Œ ,ƒK²"E(GP’\…—a yÍ´·¬ ž~P!±ÑgÐhÂó7& ¬VÉZQñQ€ëýÑ æ¤¹Ž kë`†h§“N¥á*ÕÃoT¨ÂMÝ¥p^‡ÃŸtgC¾ö¶yt*Î'ˆ¿ïoAÄÁÎÒ¸‹ËiÜ•UÔ?¡ÍwNêÓ5OfÐ’ÁîS)ŒIL@¸JÝHCî„9ü:ÀÉÅX³\£_s“$„þF¼ißøk§"·8:¥åí#ÃHÛÉ3&Õ1öÈ]B3 š‘Ž*Ü€ ÑÐ V[Völyk"e?‰Ç©â&Ž¢åœìÔÝ–»½×njÿxϵfdCà´À0¥0¹Í€*Þ;ÈûƒtJs}CP¸•ŠËl´Y¦ŒªüøXB¹ƒñFð‘¸Pq+ŒÉÈnN|DöسÀ CRæ÷Lj“¢¦¿„«Ô·sXú*\bÿÓ gb‘ΆlÏFë‚& ¨§D{{ͺv:â}Ÿ3Õ¯7nJ·vL‚!}Z¸â|< ½S‹[µdko2ghTP¼‘$©Žr==ÉhðÔïû ºJUH£n±ø5!ÓµpÈŨʎ/¬£`ayqw"Äñªæ)½îRÓ’«v•zV-ÜqbUI¹ ä:Ä<^hò‹òü$'C¯‡pP²d,I–´«T‚4káÒi°=ݱÆÜh™lñíùÅbF¬*Ù<¥Ëw)Y9J–ƳRÒ®‚pÕ’"ÝZ¸ Á»XÈLÁ.o^Ä,Sa®Šz˜àûïmzâÉmœ”1q'‡<ù%kÆ’E‹ú!\•n(já.YXvšXÚ ÝA.µÑ2¶ˆó§Ö8ÕäëYlІÓª÷ò*:mafåf@(zZšU;]U¸H2, X•Ø*_žŒž ÞPUœjr}aI? î’E׉ô]¹ŠÍ€®Z_Ó¤‚£/ɰÁi‰S+G‹\ŸÔÊsO¬@+UÛâ -ú*6—ìøè¥5}¨®JÒÝMÃⶸdGâ…ŒŒá³-‹ÝŒÏ¨5/>,àõW´rŽÄN‡®Ô«&$ëÆR«>€vUšÖ*\!ŠYÀ’ÝìWÖºøâµ†Vމãˆvv£zÍ‘—¤;9©<V‡TnÒ[…+À´r$NÇebTÉXMU’ß$$Ž Zr ›uY˜{S¢•Ž6 W©æ˜§¥VLa$´rÌK-Ò9×eÍIZçËÕšÊÝ $ŽõZŠg2£z WI$œi9# »_šSeËiÒÂË!H™ m<ûìñ¤­FÍjíûÛ("mÕ¶Š3­zÈâMSªè¿íª¥=ë*¨Â`<åZÃLk*R‘§ÇœTö¬ú¶Å¢PÕz­ÍônÕ[K‡¤°Æ˜®…ª«–ª¨Â‚NSªö0Ö­G•Üeh®¹¼l+ÛÜÈ»69‘š õSBÞxüЦOœöÃU“j@»*M›Ä%êÌÅpŽ”VŸò”dÄ“ƒI> ±i2Ó‘Ç…Ì1¬¹s7×¢§ê*É’Ö¼dÀ«T]•ýIÄmZ%U¸ˆP‚ìˆâ¦U’ÜhVUgw8 ñx*6Ô'D¡ª#<£ú W1NUmõ \•z(UáÞNë_%•­BìX›ÚhVUÀƒ”=mJÁó_Ç&-žÂÉf½ÔåIõÔÓ|ÁN@¸*=ÜœnMZ?ÈŽÌGeåebù[uÅÉ™+)Ž$MŠˆcƒ6·-:Ñ®rªíz…êªÔ^ÝòU¸æ´7kKF‰Qàqˆi£EÒµ±xi‘¤ã=áºþT LDDë5º_…«Zµª]•6=õæ¡°%6A¸jñPY®’VŽA8YÌEÛhQá²™òHÒ¢WÑæ¹dD-ZU=¤Õ¨õ T]ma‹Þ'(Táªa{dG )S'mgDËú©viÖÕì6 Eó)òtê©ÇH­§ªîÎwÂYÙõ¸õY‰¸H‹:ƒ4#)÷8޽“š=¥"’òI ¾¯Zv¡º*ùPn|´dÙÝ©T8½ì¸Z·¨"Õóõ樂šÖkø´Š ä4—"gÆ ÕÊYƒvU’¡-\¥SN‚숣š¡`ðhÊIâ`Ø-ŸX‘`®æz‹T<ìYIj=ÕUÜSÞê*­Âž‰G4ÌtDM÷kQs%‘XJ*%Ýß×‹Ç L\µh«†P²ÅS[!K?•¢p•ÙL’f3>LN›Íh i:Ëî´Ûí´ÕŠ_“$].-AêÒÂUâ6‡3Ê4DÒbÙ”DîêÆãŠU›5U\%)€4©ŽØRÑ È„‰1‡Ñ…ÑDÓvÚi²ÙÝ´ÕI”‰C¡ ש®ô¥n{\Rˆu¸q%•ÊËoÄíy©—1¥m±*©C©Ñâ*•è©Ç ^$«ºê±Ç\®u^Ä9„Á°ò¾ûÜîUvúOóJß®­A(Eÿ«ªªÜyÈó°(Êlf‚‡[­:Y„”ªpÕöw–ÀÙÐ y\\J¥2q9œHy5®ØåØ ýQáæ@Cr¹’*“ k]“%µ¾â/]®üuERƒÝ½ÒívÓ«rÿ´¦¦ñFþ@È[m·¯ G0xx³U~3 •:?´rpgLÚï׬›÷–B‚pæ?…yCcô„)ò2­¼!é©Ç€RuU|Í€Ïlv9Œ§“$ÎÕÞ¼Õ«}¾Ò*"ŒZ_iÏWŒ|ÒÕ¹»$Ib9í¦ssÝ^’´ºr<$©‡UHíU¸ÂSl’æÌlÛa¤9ñfMÔknX¿$q$ÞçxB'm>-Ú+Ë•ìC)•ž %©õxª«˜gžñ¸ˆ8Ð:éÌË[‡xÇxãõšºÒ<bŽGëêjyâÈBÄAÒ´Û½:7‡Ëš“£ âP}®’¬[FÖ^K5p5¢„7¹~‰æóMyêĤnö­e¡|MœiÉîNÅ5,ý¹p•Ëe6›6Ûj·Ï—çË«A<áËóú|U¥y¥ˆ>J³² аÛòaà”G]ii©Ýîtž=ûÀãÚß½úe¥ÒŒUMµH¦ûµj$k:¬éÚt`Tc ƒ’ØŠ{²WÍÎ"âpšI“Áfp»«kòòªª|µ^–ò|ÈÛ@ÈB>ZB ¸ŒÊn/**B?ª)}½ ÇÏí;&Ž´ïo˜Ô_…+D†O3lÕL±¸ýï© n³\SÌáT¡Î®®r¹¬f‚$I7B.ö8¼y¾\·ÅBP$i0Ø,¸gÃn³ÙV­¬ªªÃr_Mú¨É«Ê˳»IÒB}¿§§¾þqûs²¼A«w=ÎlÙir£eJ;÷£ëuU*»¤^¦à´Wû«B=õXH°º ‡G¶;Ûë]íËÃXˆƒ$(‹Á¶ì>¢›Ý´Š®ªª©«yã Ä UyUîUa0]FıQóÄѯZ-\L«XË åë‡$¹Ñ¬)õ»£K”âÐ#„ÅUjo"§5MVW)Ôú[Í$AÓv»Ûe·Xì–ån»}å}n7M»—ßGs¡*œÏx½¦Š¥‹*ôß½|åJEò0YÏõä”Òv¬Ö«9µ/¯*åš—bñ8®ÅRª†!Å‘ Ì¡R=õPW‘²U¥Øß ÝvDLvÜñwŸ þje^©-Å­ª©©®©ªÃŸ_GÄáFÄa!‘S‚‰Ãa½p6'gÿfœ-A˜Õ¦Aš4Q…+Ä`†ÊŽH¢Tåš 7·"U #`Žf53‡jõÔc[ùp•I®®ÔåZ½Cu5ÎlØíÕ¸=ÜWZ]œç«««©CNûsŸ//Ï™eÉÊÊ2c¾`IçÌm¶ÆzŠpe»²W¸VhS„Ĥ*\áY¦Tß•šHKpµ8QT0,c#ÌÍIØî[4Á*ÖSõWˆ«äj«®Õy8U]};í«®ª®©EÄá«Þ_ZZƒ‰£¹Õ¸ ÐkG^ [cÅE©ð'Ânk|‡"²YâX៵hŽ^©ªŒúCE';ÂÌ´h9¹~GÂ*Èp,„9Ê4À%ÚÌAʆ«äÓ3•—Wäóy)’"(жcQªTµÈÑÀ>M aºÀ~Fˆ8ƒÁæn(GoãúÊ|Šp¨'Põ­0¾ýíùítÒCJÎdŒìˆD—ª|º€9TÜÏ¡ =õX{Ra¸Ê)O{¬AÇÎ"âhgé3‚ˆ+ØÄA ’œ‚ǘ¿²†cŽºŸ×”úV®$I«F‰CsèÏVŽQqrã8D©"æPù…IiOÙ^P]E*4´Y­˜&r<½½ÝbN x§"ä[ˆý î† óWºÝyy?/-eGÖT-w›Íª R}; ý];Œ‘Œ4O€ f޲2•éVEé©kÑA ç©05q ÜãÁ´ÑÓSPÀ‡¢"É "|€Dpk%&n¬aµØíËI³YM“9ôN™ ;´Jfn@” Àû‘j‰VUYÅæöÊdC÷Ê‹Z}LƳg{{0u´·—¬F¤_CD#MçzóV×”Ö²É Ÿï!w–Åj´ZUuú¾õ1q|ï{º»tünNŠ»ÑQ*@„9Tª².©g-ÑçÑǘ8º»9âX».ܰAŠª¨ˆ°¯A­¢½ÞÜÜ.µQƒÅس,F£Êº7B¹ ‡¾'È2Á9±»Ñ<"˜‡Š|?¥û¢«Õd4¢ýXoo_&޾K•…d87N‹«H‚ÝŽ5«J«ª÷WW—––úÜn»Íh$(•y!âøö·uMÒ>£A]½9FÒòת÷G@¢ÒªÆ9ä´þ%œN›Ó` ‰sçz»qœïî¾ÔW¹·oˆ68‰‡¯ w”×Õ”V¹WÙ,&#Ú쪯W<ˆCϲ#’ì$ÅÑèVªóšÔQæñðÓ.fû*»=ù ˜&<žOœhjzçćӴÙ̧ÄÑ­Íf±`ÝÜUn_QMq‘ç«jòpxŠ&‘kât8$²†Ï¬°¦¿¶JJú$i8uZ?ïl¬Lœ‡E ƒˆnU¹J™^½öå2Gº²Yâ¸Ïn÷úܹ82åq45}ÐÔt䃇šè•f^9¢°ú-K6·Ûç«®ñùpímU/Ïç[·Õåpˆ_ ¤DÙŽ8‰Cëéd½N;.q7 ˜ ïsD˜C>©6õÔãÀŠ8à´r•ÒÖÕÕùV®ÌE¾ÇršFÿ}¶ãQã4í¾v»kªªªª÷c!D<“Ï&÷zišd@×ãÛÛóóŽâØSQq|ûÛ±‰£ž…ÆÏæ´>·7âìF9¸€x˜C5wL‰^›«{Ì„ˆƒ¾¯”ëÀ¨{¨÷cdÙ³hz"½ÊN/GDB¯ZŽ>ÜnDUÕµx‚luuMÍj7ºËm§±‚A9ˆ8.®Ì{¸}PíÄÁè„8¤Ž±.*8$ÅTPƒ›´UmrøýÝùnÍjÏ1Né³^å»ß5›Ý«ª««8}©ªš×ÎGß=ÊçîÏÃ?ypòUûª|55¥>û}ö¬å4k9~?BIÉÁ†¾¾žîösùù–,îùŸùKuðàƒ%é.^ŽMõhû„êPvd²Ü”`d#;ä¯uV6׬"ñ‘bíê©ÏGV·»º´æuVaŠE•óHÝÛ˜,X ©­ªÂ_ Þ¨©FÿpZÃWj§íöU4N|ä8qT âð<ˆ‹xÛÛ×åßmá‰ãÒr°éÁB Ž%BÀ(6TÍï'±2Õq¨ÁMކ>¡³µ™æ8™Öææã)¿:¤œt“td‚ßXþpWAyK^ß_SWWSUZZUZWWZS»¿ººÚ[WWU]Uõh­Åf· \]…~ƒ¢>¹><|çN `þþC,qt÷žëõxèåøùI²½±··¾~G³ª!Žï}/B˜.Ï"4žmÔ|°Ê«¯\ÜL3´ü¥æÊ¿u¾AgÁ¿©òy‹rgʱ¨Uʧ¸h\O=p)."Êl¦H¯·¦º®æíGKóªªê0ƒÔVWW•ææÖ±Ÿ­µ[ìŠ'‚¥šë×ýÃw¦³LÇæCø æœÜ…ˆ£ðÒ%‡#Ûƒ·§™ Åò'žÞÞ j –9®Œ0˜8È‹í½½=èOO{ŸÇƒW=Ýç»ëëóóñlÁ4vÇ&Žú4ORQµimãœ8+½IÆÑ bæØ¨³#ÌDìG¦xj&dW)ý2COÑÆƒ8½ÝÛËÍÿëîi|‡ s{{__ýA3qèbff6×á@{ôF³‘X/)¹øUA½Ót©¯»=º~횢âÂÂcôõõô468f3A,³¹¾‘ò7ñ§,æ#9Úèa¡ù³(•Ñbzœk.eª”àõlÒY'`dç!c>SKA’:b\·:9âèæQ߈ˆ#Žz’üä:zë DÄQbCÄñN£ÑÈ%Ò½%sº~Šjjê»ÄÇ;kÖl.*,loÄÄQßèpb¡D‹Íµ"l¹©ª°Zqp¡*Ç´DDÍÍ“z»ÐFÆÒß:±IÊGtvE¹ci!Ž ÐS缈ÞngêAäqß6Ö ……8ÁÝp°¸xv:˜ž¾3dŠý……=ÝF£ÁÀ RÓ7—gQ„Ãáò8\—É:tÙ?X\l5]lïéÞWi&ÍæúJ’ÜüÇÏ<ƒ«¸’úÇÿ\9ò'ŽF¯¡Ò`¡ù ²3-z.Â;ÁF‰Ž¦ùmH‰cýû:[ÎÆ”U«Æ– µ\¿zêÑÄa’@ı‘$×næ‰ã?ãú_ÇYÇ Æk9¦DÍz+ mõ7Œ¥7tq$Š9tWZU¦¤ZµÄ¡[=u)\Ù6G7¦Œ¾Þîytw·_âf|¢¿‚ ÌÌ0œãÀí~OTby"êøÞ÷¢Kqu O5ýéPšJܰæù•(æÖ›&ñX§ÔÇ´NõÔ£’±Üí0öööp«–"gXŽ80u‰SG&ІveGD›tؼ1·Ieü÷$ıAw‡|T6ÍÑšj“ê©÷ëv­ùËïfY0qpJS==çYé½ÈQÉ¥¾Ê}¾Ì¤>ùyƒk×45®ÝN¡/(Ã6 ®+!q\ìkl•¯Õ‘Ò7ðöÛÊ䡎ÐW»wÿ}tt6‰2g‘ºÿܹæ²HÛ_‹>ED‡¥Lj™ôÍŒlÐiGá4G¨›#ÅÄáÔÕüæy‰£ Kõ°5.¶çxœ6“iÕrìIœ;vì\¾Ë` ñ„ß/!H‘ìí 0ãÓ{Klä+ÅÓ8 twïÕ5qqvŽRµéÎ4ëºYœÅdT³Ýˆjþ¶¹#7¬ßpdLëÛ\kss«˜”Ã}€¡nŽÁ¥†»£ôÔõKßý.Iž;‡»Å1y`úÀízÓÓAfúöኚ6#‚$KDJ¨RrCÅÅC—qkpÐ[DP½½ë›Þ©¯§ˆül«Ùju¬t9œ.W~v¶Ë•=›Lk}[„hâཽ{Ñÿ¿W€Nˆ£X;«Â´xù¤N¯°§¤¼¡²1ߺ9îL9뼊©cL²/ ù·©i1Í=õàwWdï9œÏÀR|yhp…ˆÃK½½•q”+ÛlvZs.æ K)ò:bþ§oâNhÅxAZ¼\§é ‡c“žÃéD(¡QÞ:ÇDßË×䆾IÉ_zê\C&޳g¹ò[œïí{àÁ’¢ñ›a‚Àˆ¿äÑJqc›¼ýùk~‡ R500~“¦±gb±9$õ'AY²\k6óãñ‹¦j’S0š˜ÃR…xp“ÿ£CÅV#ö9qõ÷ççótAEzÆI–8¦Çélü(KVQ‘ÝI— EX,kÖ”<³o)6qìݽ;#ˆc@-HB•‘VÝòFq\‡#åÄ!’ˆts´ƒs!¯$;ñ ÐS7[­.üϵbófìq°mló_c£Õ51nc/.ÂÚSÀÄçƒþ"<pzÚë ‹S…¹Ö¿‰'" ’â tÎ4æx2­oõ?°ÀÜÞ½{ÙPU,¿C'w\ û¦EßâTJÄq4X‚ˆÐ‡Lu Τrrlè©cÞ@´a}lE "Ž gÙÉqç¦7øroï¹Þ —Êãø,¢iãÏþ Ý,Yö<”©ã­·tpvÕ/;2×éÞ(ŸÒõr6#”Ù ï÷ª&/–Í’saÁHMnëd ‰C"vê0&ɬ\é]g´šLbã…³\Gooc»Ó„cPM#â0bâ@Ή‘¦-YfsíÀø\ÞåN !ð8ŠqŒ³ÄAQY–‰ñϧ×ä#â8w¿Jˆã?DîÃnG âÐþù–LU_]•phS¹Þ“ÅGUÙÁ¡OŒ5K¨ƒË’GÊ0R99Vâè÷ë1™EOoï¥ö¾K¸k©÷õÕ×;]Ãèt^¿Ž7æ¸¢Š­½b&>÷û)Š2ä» †Ë—?Ÿ˜¿3 úý´7??;SL‰wf˩߼i§ó]33“#ÙôgÃÝÝ—(Ãñ?Ò†”88jгÏQ¢îX«°í¯eNï‹ÙLX©jðFÊ1Z.¦.KvEÊSHâjFÓm=^‚ð¸z/ö]dy£Ó¥ªoð 8˜8¼¸2ìl „ë×ý~t$ÌÙùFãåËŸOLßÁÄ‘ŸOççcâ KJfØãø8MggÏÌ\ŸÈÏÿ쟺{›HGPïÄáWõ 2¡ŠúñLXÌxŸcãdz´–‰©£miç$÷¥„8Ä’ º$ŽìlŠhlïî¾ØËzXØù—.õ]:Ô4003ƒKqC³ü®’ŸOÞ¢A 08XTÔßï÷OŒOOƒÓ3³ÁÀ,Ü¿™$GÐOG®»³½>,«~¹Ÿ °INŽÉšò7ó?Gˆ60qÈýž~‰C,;¢²P•7Z3c-›>òäû# `)0w\­3‚òÔžW£S›"A|ÐØÛÝ!übŽÃ‡8âÀ*n–ßõëÙn’,òú#Ä1>~kš b/W] ít††ÂNOÏâìÉš `Yì—`™‘kþÛw\ü‘N ®+`56Z¨%õ78p÷ÿ™ò¿«Ûº*áXu ¯ÊSoRef’,yóhyYª‰C8üÏy[¯GÖá@ÄÁn∣»}îkjBÄÁf7Xâ`æãã¸7Çl™ž MGÁÁÄÁ`âøÄ?}ÓážòC¿"É ˆ8( Žô ¢šC©ªCÀàoh“c#Ú,{“fÉÅÄ1šŠ¨ÂD¸š‘œÐ±IØ.Ç¥>nlÎv`ôÞÞƒ³,#×q¨Šó(0EyqÙmqñÀ€—özýƒû÷ççã<Èl¬úûIâúõ¬,,s8>ÍýY,›Ÿè=çÊY‚·ñé§r´qÈQ‡>ˆ#xÝ©FÞ˜ŒÄ©ÊuÝö§\ybýúÇ×?¥Éj0Fš%6åZ~Þç0ùõlÈçð\ºÄG/žÄÁG£Å21Ê^`â°Ò-Gùù%%ƒþÍ›‹²óCÁ ‚ÄÄAQÅÅãl¡n–åàçzŽ%xÿ3éÄ¡/3Pì¥Iï šâTÌd¨¨ëÌ“áéšô:fZŸ-+[Jâ‡h§“î×»a8¬FkccxT,ö9zq~?¦Îߘžž™ÉÏçd )Âèøü&Ö¶åBYœŽÕôôáZ‚¢oÑÌÌ,›Ám€GÖÔǧŸF<üµÎ æ̲䳘>nMlÆ1‘"/N¦ã"âð!“ÑšòR\&Ć‘rÞ(mqõcD¤²õ¤6©~¬ˆ#p¹LÆd©£››8ÞØîpøý8=޽ÿµ’ìO°â"Ô£µCCׯ³õUãxCû«ÁAÿ£µ$éÎ÷–ànrì§Ü™Æ}é.ÇR´þÝŽ‡6â'FDo½Ä¼àð®Dþ„&]qfTŽ:Àá]\(Ãå4(à&ƒsÇÛq r>"Žqá2–7ˆG/_ÆÄ<‘;¸,÷òe¿¿¶‚$ò³‹¼7ÙÎ&8>M’—sIˆ#¸XâFÇ?q¤:jP—­ðDÔìBmæ¥æZ£3` Ç7V<³9´:ÌæO}nÄ«ööÂBoI€¿>RTD’tþ£ÅFkdæ_VÖ£›‹ŠŠ‹±ÌºÉHׯOÏp%33‡‹Ùš—äM|*@àÿ ÝûãKiãÇ?V¤Ï¹·$ÓHÅÕ[¼¡±}åúhѦ²XT/ù³àò..×3Ï â0šÍO}=×?Žˆ£¯°pmI`öæøÈ"‚¦k=|Ød"Âcb³²öïGÄq3‡ÉHˆ7*®_ÔXÕàÇš—ž8"÷&D¬Ï îˆ#5«Àßøw8šÀæØpB£þ®¤—< ø­o}ë+h¸RÊ@âPªa0Ô×÷t÷œÇs9 òæ¸×k4ïôã «@€¦#㙸ú*‚äòýýLðv€Ñ âª*†1›±o²T zqÞß.Œ80Â΢F{¦-å¹R8ÀÚÀ‘õrxO£’[â,ylÇ·2Ð*Ö¬ÁàqY±/A˜¨úz®ƒ¼¯oãk9~3Ûk4UlöúGpå&ÖáàxgË nG?¯¢Ë`og?ÌìC–ð­à4ǧŸ2É ŽŒð705J鑹þœMú+ ¦zBÿ†VpE–8Ö?©U!|A/ùbGŽ+Œo;ãì“C’6ç%Œ‹ç°PU÷ùÞžžÞî¾v:Û`0Z‹‹>øÄÇo²^â ³™§–` l$8åtbâ0l¶¯ýÞûrrl®ÊÊlÆ?½==“›w°8Éûñþ^øó7YÌG%zÁc+\®u^³Ùá“€ Âéô®Î[íóùJk|¯×ÔÕUùŠ«K}55µ¥n¯Án'ÍAÓnwn®;—"­Öœs’GA!îqÎ?Y]uŠ›y2š8ÞâÈtâX±Âl5 ÏaâèY[HrrN\"VçȆUªb+¦‚@q‘ÃEÓo æÀxýõ•÷‘$&œ/òK[Oµ`â8õ º&Žg\ù˜8p6#oõêÕ>¯³"ŽÒ<Ä ¾Gëjkrsyâ@§Çò\w^.ivY=9ÉŠ8F ªšõy˜ç„KëFýu–lÐS¨*9Äñíog8q` Ÿƒ2 õô<ÔØxöl__Ó;f¢¶6ämÌÌf±\úð°ÃE÷Þû:⎷þ×´Yp’uhˆËƒxÝår©ÄWçÈB>Pd~ñ!âøÅ[º¤Ž€Ëe6㸢Û]ãËñ¨b‹<_^iò<ððÒÂieÙ‹ŠðJYRñù,§ãìÙûHÊ_)¨jÖ§¿1¶Aï»ñ9iCGfwþq„ˆÃIˆ86bõCï46CÄñNEÖ>Š= \Ùû3Æo"âð;uï}ØéxëÀ?°ÛpÂüòeüDÙáÔq!€>}—‹4Í6“Û]U…È¢¦z¿7G©¯4ˆƒW®$)»#œï(­+-µØNLÉ8•‘ĸ>… ™Ò.=¾Ë©âP  & ,chrØlÜ`§ÞÞöö›(ó'þ™ªÂuS\Èjbbж²›íõýxóp`¹}{èC£ Uõ–~,B&ò6¬¸ÅÆíu»Wçú¼y«óòpH Ÿ,|êì6ôoÕʪª:̈X|¥¸Æ*/oÕ*’4.¿ßÓS__¸qñ›Õf}âÊdt¹>†Õ¸:ÍT%82Hï>&ƒ%‹ˆãâÇ’F?KX)—a;ŸOøÍf Z}öWÿ¯>®¬bÛCC&E¨ê-q¸—»ÝÞ\\1…ˆÃˆƒ¢ˆq8m6Ûª•yU5u¯×øªpaU]K4ALGYâxüñEÿ)-ú.Ä‘irès…„¦:½›Ùú€RÚˆ&Žzs@˜àŠ5N§¢¨}…çÎõžÅÓÇf³y|âæ8?ñov÷ùÍÍÌ ô»\F#M;fó‡—ý~ÜHîtªî-)ÑF0øË_ê˜8fqÒŠvÛín·Ón±Xœîå´ý¾•«Ü4í^µª´ ç8¸&œÔÀŸ}>·{åJƒä…e¬Ž³gsrŠ7/öO‰$8ZôxÉȉ9=©ÛbêĦõ›ÞÍìÄx0ø•yˆƒÉ4â×”8¤ q ê8wð£Álžç¤G¸þpì{ôWxqdg#â0~ø>&ŠâP ¬f’¤iLö,\1…>Ó+ïs»i;¦6ÕáÃ!ªjDu,ƒ”V¹ÑÂÄAZ­çÎ:<ûJ 1Ì»]" Žfzï“ß—kŽ;d®Çãõõ•,ud”–§kE¶ ¹A6«¯w8²ó Ò?8ˆËr9I'¶—<ðѵ¡Ë¿ºÜߟ¿«Œš“” QÇ/©ÇPUÞjÌÕÕn7&Žú‡o|ÕÕ¾ÒºººšêššRÄÕ>ßê\»ÝbÏÊb3å!ñcÂfol$‰ìlŠld !iÖsb\œ0Gª`ÐC†G%Ë™Fßpeg#Þ@ëGc#"›7Ÿ$ó³ç¦gæ8GŒ“Qõû/÷_Fı҃“IsoTÏÄáp­ÎèªqÔT#¦ð!Ò¨®.õÕÕâlFMm5º×çó"âȲàâÜð1¾±Ù"ÄáZáâˆ#á ¡Uωñ™'dÕ8@‹+Èã{ß‹¦‹³gÏ⌠VÃ˃˅å ÖÁ%¼%xæ_II1º}¤¸¨+WY .‡fßæ/ÐßI$¨<Ÿ¯ê!/EaÙBš®ò!÷EF©}U]m· ¬o‰ ðI&CÌa08M‡RM=äÍÍË£Ñ#­¦„%H&ü¡G¥‘aYÞ•5ƒ‰‡¨ÎòÈTâ`= Š*)á•’‡ð¬Øï~¿˜&Š2@*%—ç+òzq.A¸í¬’!r뉃#‚õ6ˆP‚ƒ0L¦£— ‚^å+ÍÍ]‡é@Ä‘ ËѬçGPF‰cÃFð7‚SÃOn|ò¨~SèÊ*ÖÑ £Í`Åo>—V•ŠdÅ©ð?<«CãÐ/m`ét•—››GíÇrr,¯×çózé• ER¬‰Ïê§“  Ì©³7ò:òò^ߟ—WWS[‹CZ¸€.±¿!¨*Óc£ñœž&%qÛvÄŽêbÐ#“q°hââJ+,«J|ê¯8þ(*Öü[Ó9qrݹ^’<ÖŽ‰ã!L¹4ˆ‚ç~Ž-ð÷ØÝÍfv`&Žý5yy5u5¥¥ !Ž)}wpÈÇ0ŒÄeÞ:`GU¸{waøÎw¾òßâ?£;ßCOýaãF|ƒ>m|ç'ç}® _Ená Q{¨ OE¡Œ¦Æ³X€ÖÈÍh" Vi„w8*‚Ýpôa2®\ÉÖéâ._éÊ•‘p†#¬¥®SiCinüû¦ŠnˆT3w,˜80s|å;ëÑúâ¯Xâ`ÁsÆ1oüð‡?|ç] f^ *p¢Õ|ö&3ëJP¡@Að2U‚2*öÅþŒ L¦|L¥wø|ˆ8ÈD‰c´Œïá(×iüæ)SÍ{P8îP)£Î³¨¿÷îÏš5kò¥÷¯ÿÎwcÞáhÁwyê©²Ž¾ùf ôï= €Æ`´b"ðäô"ôtð¨0KP¡ða‚}ÄÊ•n·//ÏWSÃö•×,·›Í‰f7p$§\ï3ÿÆD«ã0ì«fäg”?©JêPdŒ÷BıfgÍý’åÿ½p¤ ùOá{Ÿ:òÔ°ÃñCÖßøá;ïüðYð8ÚƒÕˆÃNO7‹ ¹&U„sŽXñ_ˆ7róò°I^UMioÕˆ£Uç38²¸›fÀ!—‹TEôtUxH—óukÖ­C¾ú`?#Ç#D,Iü5ïqðø/,oyŠõ6~€= Dï¼÷^±QçóåæY,Æ»6@6¬5R®ãÑ GÃ#8 tËaR76<~E¥Ä!÷ƒ?ñ¯øF¼-¸‚ñÑ•O®\ù핱ðû#ÂØÿ…¿:60ˆ8(£õG½/®-á²ßdHW„à äÖë`=M{½y¹ÈËà‰Ã{÷]F㈣EÏ­‘ur^ߟƒ‹ªR§‹ÂÒýi˜3>ð‡à³W~ù¹;nÊâ¨V‡Éa0Ĺc½½}=݈:ú.m¬d+©"[•‹s;íÃS«jªmÔ"ÊXn³™ŒWIµÝtÄáÐù^œ™™oC€MŠÌ¡¾Ž@…¥[HŸþáÿá¿b¤Y €ö—1Ž8¬£Á@ÇÎ!âèë>߃ˆãµBÂÌõh„úÄYƒ'ÚŽ‰ÃWU½ßWSËJ¬#â0Q„Ù*óôñ Yï™q€<ƉC}S•<ŽOE®&ß |äi\¹r} ÏÉ~u`iñôa—‹¤(жßGûV»Ý˜<9MMÃMMM'>8ÔDÓ,Yð¤a±Y,œà:ÍeÖ_G‡7Ïë[že6“¤Ó)U“))‰?h5¥ó€âÖå¨q¨oL‰¢Çñ©(Jõ)ŸèG„7€8¿b1qà\…ÆÄq ¯s<#Úh:ÞtâК6“‘þ ðd¹Ï¶W¯­ÆÄ ÄÉŒÃáŠ"sÜZUÇCÇ$œ˜L³Ãá òÄñ^¼óâ@7xéždË _ôáᢵkKÿkˆ8>\ÇÖWý!89|ôÈSO±Äñ›+ÿå?ÿÕ_ýÕwZ®„uU掰ÏÇ细ß}}d0z•÷ÙûOè‡8öÿ_Oø§ßýó—ú¼DŸÂøkþvÃúxàßøÆšû׬YóÄkÖ|ÿþoÜÿ70îÿú÷åüÇý÷£ÇÝÿýÂ5Ð7lXƒÃ>úOX<€.óÿ¿½s‹MëÚ<àÄŸN&ç ¸‘zZéÓ÷t‚!“9‰5o#}j*sÙ±¡†žŽfÔ§óxæñH¥¦F0ß@úÐxâ„KÚ“ËÌCÓ¯•FÊ¥i\\zJ>uj.WIw}9‚ æbÌf{ÖZð³1`;†íÿ/qblî—õÛÿËZk|\­Vi¬V‹‰¿“I…~ƒOYÉþ†ÅîÚ_$¼ë° y_g5Ž|rq'õ".»ô¡ÏÁ¨ÞÜâdõÚ¡›&'ƒyßðð™3ÃCg†*ǵèÔÐå9éÇ q¼óæ… þùÍ ïxßòVæ¨ðЏ#¸2/²H’|W9ŠxDâH…C˜»}t—|)HF.ÛÔÔ”Í%Éú¥e“I¯W*‘† Z­žÒRHDeShÈ—†œ$.BÆ †‘2°fd†ˆ`*â@æ0Ž«u*UÙKË[‹‹£±»V)q@»Ñ„ 6˜@¾MWͯà¢7{|Áàb®À¡H?¹ãµ¢‹ëª·EµôÆ«ºùq?èuŸ>3<ŒBŒáŸøDÕe2Ÿã:úŠI¼Xù¿ü3^i„¬Råµxƒ[BŽ8ïo±@ªÛíúø‘BªŠ ÙñàZ‹« °ÙImÊ!mqXõ¯+•( ´úaäƒá!$ƒaMèµèGôC=ú—6hyOhµ§ÑIô³a­AC¬‹Q5EÐ_½Z‡Ú3Žë‡”þ–T:£Ncз&ŽÛpô°ã޹€£¸µKï ¶ÒÁ‹¥Ûj)sûâð–§øy‡G9ðÄ¿ó|ªjX;Œ—9ƒ§zpì÷þåÍ o¢xãÍ ^\ãÕêaG¿¹ƒ$ôÍ›8Ñ&^Ûü‚Ñîߌ_3tg±4Ü4ª?ÃõðaäíרsZR×^&U,_÷¾páÍwþß>ÈðTKøÔ5wðQE%ÄZ$7ƒƒ'ŽÞòê+½+¬ ›˜8¦žöþÛ6*16GåD"‹Ñ±cb±x<¾ºúüù*ú?¾ñ|õ9:G'«èÿ4:½À0 ó ú}ùôBŒY_ËåY–oÈm¤ã™Lf)³´¼Ì$ ¾öX,›É,§Wñê­7-Ž÷:Å;Ý}êÝÏÂLôÒ»e<îêÞ,¹JÚŠ+W8²¥ÞǺËé°;®P4j’¤ªàìý÷lXðpìÒ«7HÑ ÔÁ$bóH X±…?³àGÿŘÀÂ~¾A>ÀN@çÍ,g2 òEvm}­ˆÍ±§él‰ƒ`4ÃdWÐÉ¥d2IÄÁ$ߺDàhO»C°3ôâhú¹µŽîd¢zÍд ˆÃÂ{êÇg—G:†¯cW\Ö’DU¥;7Ôâúö>ʸåöž:õîd„ÇgXÿ‹C*ó•ëáÜbµ-«wÄÁ…5Ñ…Ãi›Ú†tÏ¿#‡>â`èX,¢? $b ±y¤‰Ø<Ž*ô >GYL ‹#‘àØõâúz®È¡H<·`’Yb d $¡C§“™¥¥L2]#aqpOêë´’«ºç9íLùÑ݇Ô8*‰É w™ãëŸ=ûñ=¬Þ!˜¼[Pø.Í_{÷Ôéª8"ÑÉrÄq)ŠO–¯«\çKÞb‰#Êw¥NµEï‡9‰EPÛ‹ƒ'žˆ1éd‚NÐÉDœN$’ ¬,:YDšH0$)Åa0‰åe:_¥(²‹%.gæ‘4–+1Xá5’YIÆÑ+€Ì#ÙTU¾Ñô5h*ÞëÌ`4ÉUqÉ&—káEÝœÅëÝL#¡@WÇ?Äâ˜Á]V«,`Å—,*UûÈ$ò/¢Š„Þ(‹ƒŸÓÁ–‹OJ½"ŽBÈ1Õ½r8§¤\o.z‘˜#¤Iœ Â“^ÌšT*8^Ify‰N®®úÉ96ЏÆ1¨ˆdb%ÉdÇõŽhNšGÞÛp ‚úø^#^ãhÚÕ$â Z¸UáÐ÷âT•›¦—Ü‹ÜÏ~&Guý*á4¿htžÅ•ˆãÒ»ïþÿ \þ¹êruœ\çæ¾$]-6vNuJ§¤\¯º“x6·É[âè;¯«ôæÛxà_Ê,//ÿŒû£ŒC±ùYïÌøŒ1ó¯­¡s}nË[”å:Í$WP42‡'øéõI.·›ÏzI_Õ ôéõ:Ý"1ÉâJ‚Rª”J6™(5ïªêIh‘T;ˆc¯ v–rj’âjå5Ý´Åkq{+DÑ]ìŸý8G:s/o|[‡÷Ÿ¨ŠFOaMœþ¬ì‰J¦ªš»"g#=¸‹´°G·kÅÁ¥¢a§Ã>µ#zþ>vVÒâ MÉs9Y”ÊÑѱ™¿ãt²Çm"jnH9†d`^œ_`sO<tn<ß\3t{%»Â0… d½ÚÇm¬Æb·ŒXã‹+É ZoÒË©ªYÜñ«ÔÌ!q˜ø[_ã$³FèèµXö8WEuÖåÝÁ4Žš¡;H2U•¼XÂR^·ªÄ>!‘HU_\Ð=¨V¾à#ާ^;uêÔ»ÑÚ "T6G„Ãy/| ‰€]*ŽÂÝÿ1µô~Èá>œ¨¤ÅqÇb1y7UNUqüD½'¼Q„Es,Ž“'G|Äq‰ãôék¼8>ØôÆ»“ÑrÕ£T)ràÉž|7‹#j›Ú%z>äHÕ̆—´8¼Dè/‡fì"rÄǤÄù˜DJ¤‚¤àbŽvã¹~Vô×cµê)îv†Ž-ÄD>æùœ†àÃó<6LeÒ2ØAé ža®3úýñd‰LDâ8"qx{g")šÃ¢ï¤ÆÝ`s½'Ø¢é…C·WÐ,Åñ²ðžýòìÙ{“AѼTš81¨>qâÄw¬¾àáý[dIâF¹.ÎOæñ2‰¤R¥lu‹ñ`©‹ÅÙ‘7lR* pœ°Âc“Xy¼F¬'Ÿ<÷×)Õ‡KËÉ÷FãÌÍ›·>ž¡“ñ oX­¾'AŸÕj Œú™•,Ž9øC97‡—®R)ÿ0ŸÛàØRЂC%rùLfŬÔo3L‰C¥R]ÀÒÈñ¸h¾œ‚UG^Ì ôšëåaÚ¶7*É×ÕÉÍn_nc‡ZÁÐ]n²u{Y.‹‚w/‰2ž!&ßã{¢ÊJé»Aõà‰ƒ'¿ ”²>~Å‘Åùht>òj„Ñ7T9R)–óV®ëfq8vd¸¤uŒ•py¼FIrÊcú™û!Á\çÅq·Býÿ~Ü‹ ™åtÒKY­fY˜¥Q<¡Ó]¹É➪ ²Øá„kC9‹Å7X¶D[-&³šÎ3™ Q¥BÊÒ Ù\ÉŠÄ¡’J´Qïб‚7^hÂFÇ3É\™ZÊ8ÑAŸ c1ßöŒ`è&­²¸Ìañ’?÷äed_žýôÞä¹sÕÚG)ð×Àùõà ·&ŒH»ÉÔ¿¿E"ÑÚ*G4•âç º=t©‹Å‘j3¨óLº¦ «çëɬ]º³ÇkÄAóâxÂq ÃÐ3D7—3K¤6N¾'͵ÅtÐ@™¬5›Èå¼fjH‡‰Åá//«NôAÇb«qNäM&‹eh6Ë,Í\ÃÉ®$™¢Õ [—Î3šßA•ØÍȃÿ¯˜kóí³9ts|Âçuã$•ÏéÀÞ@ütfòýI÷­Ê-ïˆ7N jI¸á-¯-’õº±->Û¬uTBsð×îö•ºX\¸mØÃéºUqKiiÍ}pIwöx8‚ä”—ŸÔÇÌE oâŽÜ›cØ"3+åé|Éi3e2Ü $Žb±tÇlÖép5„È຦⠃¯y…cs7‡/FgxÝ\\Ag 0F_—Ô<Žbƒ»1ᆄˆƒ/VÐI~¥Ãkç'¯ÿÂóõ¹‘÷W P¼øöÛïÎ81¨®æ¶øÃ~\*ÏçàC¾G7ŠÄÁ9<ÉnGmr¦¾M*•ކ\NGU\}b+R›êêý9€)éί‡—œzRŠ'f%yñWn"Y,Ì»\žÏGë(=E©f™@,–Ë•‚¥2z’q,Ž€¿"­ŸÄü¹ Ž(ÉdÒM0¥\)1Òó†‰'9äøs5âÈn©Œ÷RªÊåÊW*<†ÂQôM¨6o•®n~$™V‡ Üo‹HU¿¼z¶ô‰#™üd¬qd–ùLÕÏ|¼‘)Þ2P¥ñ/ÐDwð"êzO2Ió±ØuÞ*l‹…ˆƒÆâÐÇb·ÇÆÇ:_š™g~XLHoæxÝúfh¦:8â(–·Õ@ï÷äâüO?þR öo|÷MÓÉ­Ù´HR)ÒÝëÍu·8¸†ÅqG¨áž~›°‹Êö´>¹#ÜN´¦ #Uqdyqp%:ÁÄ2¾ÆñhSËeqdnàxC­%bH$J^ŠR*5x-]cTŠsó±y’ªÂWm²˜(jÖ?cÔjô‹t2™`$¹äˆp &ý$q$MOÜ/[Ø*ŽoÿºÞ°¶RY³J@4õ™Ç[3g¼;ÅQJo-€»ÒcnÎÕ`ó&—´újfKj pè¦+;òÅ™X¬b‹•jSÕ'odqä}[½Ã%Ðtè& ;„ñÆ÷ÿV¯û¾IŽ‘Ng¤Ò’GXlŸXiMÇ„$¶%îqÜáç'QÄ¡¬xƒ˜ãæ¨N?d r¥Êòž¹"M©ÊêRÌ>’šãóT¼DpÈ¡¢ 4›HÐ[0~½ ýH\@àyã‰d2^²˜*©ª.ÌUeEöò5• ÙÐ]¶ñGõ]Í}ÿý_ëÄñý‚ c«8Ò©tÕÒ'zî”ܼ‡š¥%³/Ǧ88¯å-\çü%Þ‹cæ6^_/12£Ó éM4¿Ã8&—ËÆñ*¸ØCÎGÍü~5 8xoðíUÊ[dHA‰C£k‚¦ó G2Å7äèÒg¦hÝPZmš¡{ ^·{Â=qmâšð‡oœØÂî&/M_š¦.ž¾4ilv¾ô†øQxÍÔqi|ТSœXG̯ҨT£ è³×oÝþ;¿õúÊfif­XÞݕË’=ÖãÁ;³³Á<úžE?Íhj”ÅÁ0¤0^b×,Žž¨ÑÐ+ÙÐÜ–x§ëðŠïE Ã%Ð\xÍ©k×Ü5Þp¿±Åo4Ç$5iœœ&LJK›hÞ_(Ž»y8¦¤·ûxuèff±8TC(|HÆbÙ•ª72+I†Éá.Cppñç|\Í®­oàóâ8d‹'bu08ii/² †ù‘¯y('²ÙL–A‚)u.Žu¢õ††K`[qlå7êÕñÆÞm.ƒ#¯¥ùyzî9K9Ä;\§„$Ú?’^KnuèNøÔ:•FcÐ –Ê ¨g–²YÜ„‹~–@à1ÿžaè>[l#¾Æòƒy<Á0BqÄsH.l<žËÏ^ÿÄ×l2éÔ–,^£=‘Œ:Ç" Ò;ïjjá ™8|0\»$áï=å/qÔÿ°×ÅQ·özí@Šc]"ogýêñRÇ„N­Ri®ÑxÝ&SG&›á…@ÔÁÐÌÃÅÁðûûq¦&êÀ)­"Çê„F£1›¬V½šÎ//--%;Ž8²•ixf‹/H'ó>æbr1¹í´¨7ôy.¦âð¶$|»¢â…‹—ŒW.^¬»e{!õØ3VÛPe³ÕÚ!$ÁTUíÂ#6»|Xº9Z­ÿ¹P]‘*SL®$Ph\‘[#êÕ½%rëkëL †õ1ŸÅÛ²8òÈ­Å™8LÐW<Ο—]›P«ÕVeVû ÅL&™¬j£MqpO¶nåmò&[¼p+w|n3¥oi_RT°æ-°Í›­qDûGÄ€O ÿ? íƽG]¼a«[¿I(Ž$ó kvöÀ›µåˆÃ¯Ôh8pm£X6VH‚ ¹ƒ]«}+çØµ"®†#IÄ7Š,ÞB¶”ËÅ‘4ˆ:$rf–Åâ0™Ìæë‰b—Úç;‡X{ìöyQ°bˆ «¶yä rk0qè¡:þøÇ­âø´ÌÝ»R}¸Hý^œ¸8B’1gm7@í4Rp¹\¡Næ–T‡îàëJ¥Ñ8¾œ©¡Hºªr(zàø¬ÔW\Çó¾ãñ‘ú‹ƒåÊúB_¹x,VÌ•¸õb"®U[-¯ab™lf‰NĘŽÄÁ‰µÇn;”çD.émz)/l¼Xqüé‹#m«G¤™8¤ó¸C¶n\y$U.¾8Ú/»lÖ8Ѐ¾”ÍÔSȲé\Ž«ÈaM 1$°88,Ž :ÖXÁYJè÷ëk\©˜KÄÉŽÜF.FÄ‘ ™ÎÄ‘¯V7OWeb—kjœ"U/(wv*ŽŠ:êµQ*}êüôS‰«cËÖ²[ZŒjRUÊÕÕnØeŽ‚½º:±½Ýé%‚¡›Y BgààwWe³üÐÏ'Äxsàz|£’"«¼¾,Gù[ç8™`í”J« “ɬdcþÆ·¾-t§mN–ûj…©1½%)*`Åñ©ÔÅÁÖï,ëØ2©AŠó8„Z®npbM³W›Q‡PôB‰by‰—ÆÞyË#™ÍÅ+‹`+à6[Üiµ'§¹j4RŽGHv( ×7J,î´Z'ñÊêÆÂB&‹ÄëPAqq蛽 ¹ÑNgòe}:r.Ë-抻¨Ž MÎúme·¦l¤Yã(Õ/YÕ =¹…ºÃÙNÍ"D£Qâ=›fÆùEÕÇÈfNFFm6›Lx tô…_7†ðz#½žR†(ŠÒôj}å<&eP*Í^„Ä@~j6+5zýØEÆB./¼ÕGMëã·š\n»ÆZö MC¤€8vW½7ÒÍÇפ½£ÛÊÑ©m‡-‹cT3^‘F£Q¥4™7¥€Ç}½O×k ØjŠ2†ôCj8(¥’¢ðâUz¥TëõF,Ž-´xO‹MЬöàëðr°·ê¨ûw÷®´Å®ÏS¥¶90ÿHR¿`Ÿê®ÝÃ[Äak½ÔQ3„{LZc4Ö‰I‚2[­V“)}YM&5¿ßú£Ò˜­+¾l%’À¿Ç+ìj”Z½õ-‹ýÆd@"1UˆŽÅѤV1Zì,Åk‡Ý#Ž’ÄÅñ´¥ÃÛDk Žðû¼ÄzdªÎ—ÒªÄ5Èq\ÜNT™<&SED*¤d+‰9ˆ8T|´]bµâÔ•^¯Q©:8Ä—9Õ—:ì tuܽ+=qÔp‹Í‚“l£´%Ug›²ïë„¶‘8¦ì­U_*£·µ/¨GUšQ]Uã8U¥ÑPæ·ÈïM³Ù@é7÷qÒ ”ÁDY=|Ëb¥-Š\ÔjƒÙ„Mƒ•ƒÎ8ªUjL–ÎÅQJê;i«ÍëaíıÏÔ7T9¹íÅá’ØsÀ9¶ÓÑ}”£«¡9:ìø?ñ fWÄQŽ<ƒ>¯•ˆ¹…vYAG®nTOÖF¼S„a è^uHé1¥S-ôáVÄa“nC$;dkoÅîª#êjhg¡õ%6µ'ûåòÃÇ¡¡_&ë;tåÊ=bއOŸVÈâñ³gº|Þ?ç÷ã)€ÇcgÈËÿÈûdrN]¡Ó_}…Žò‹üTÁ|ÇÿЯ@W¹ûº˜lqøO}f“Ûë Ÿ,&‹0›qìW’j›íS…©*—ô^_V$;äÚ¿}¹P#uØÓ\«ê8©Åu‹Á“G+Ð8ß×GÄñyqÈñÕ³gšø=ò†¿TGÅ<²ò)"–ŸIžÏ¯¯q\¿\!“É`T€FÚÙh”lr¤SU[›Ëªõ>n°ÞÈí8Þkõq¯“˽ýÙ£G¿øüó÷ïñ!ǽ‡?¤8tèüùJ‰¼TÂooŠþMiÈäýýs~œ¢Â[tàdUnõÜú­>Cp àDF¤¦ÛQH][¦³ªäû§ŽtÃ@Ènѯ¼"“¸ýèá£G_~þùýû¼7îßû‹£ïð¡s¼88,„x\d`ä$:í'X,’G>Ï•6²gÿ£ÄmðdÓ)Ó.©‹£µ‰˜ÃæÜ¿Éä\ØÖ°úâj±çkp@&;¥¹zõáCbŒ{=xðÉÕ“'SôÇã,Ùv/pÜ10€[ªúúŽýÍ?¾üÊ+¯ý÷È ™¢¿½È•r9™äâgÏbÁÀ§£ ¼Hw #ŽÆM¹•í÷/èXy½¡B wjpP!Mó¿Ëâ¸ÿÑÃ_^½ª=q옉cïËò;rqÈäýHÿî_}ùe,.ŽÈåýìz©TÌáÒøêÆ9"¨GÀÎ 9vg´îhÁ%68nsï¥<°J$*öôØ÷qÕÜX(äÜ~–"[:ñzÿ‘_õzíêU<ùïþ½×^“Ë_yåÑ­þþ#GžÐxÁô<™T??1pìøxÿý÷ÿåÄ{ï½üª wèÊþ4Ë·âæsxÞÇN§ýÀò]÷N¶Ûïè]±ƒjÛvÖ“~ªª<ÒŠ ÓûךÛ$jam”×OqœBâø'«N+¯¼rû/ŽˆƒìÝ„Äpîü±ãÇ‘7ÞçÅñ›—ûñ|@9Çáêø¹ß)ä HTÀ.PM&8ºúnr!ÑhûQÑÙzpÒãpgPìg¾ŠmÎíö,Äwúȹ¢¯ïôéO®Þ»wmúêÕýׇgnÈ—?Ä‘D‰´Ù²,Þ08&—½üòûX®>8:€ëåß̧²g_’)!M»@´7’8Qqm´°”…½»6;ÚS N›HվݥˆxÐñtûKP q\<}õêýûÓ×xqܸ¡ý‹ƒlû‡Å‘#â86(—ý†ˆã#×ÿò«£8â˜ý†ãÅñO Ùà |â`7Žn>î Ú@ä„—`%ÿ’ŠtìãTò‚Sìõkéh…ã -=zôÑÃû÷ï=xðàË/n|-ï÷ûóyœªâgsà-aŒ®ÿH_ߝ޾÷Þüîw¸¦Á•ÖÖ¿þæp¿B.Ø€à.öTèÆ{Ø,I•ne àÒÂc܃ð¢F¶.ËW…EÍáhåM‡çdFâxøW:¾|ðÅ׿î;4OÄQ™ˆ["Äѯè;zô2Š:~wV&“õáúÇ7ßôVÀì Ø-ì-Ï¢Ûk„;JRmµœãeÚmÝ•¯J9vP#'hGär…âÒð矎¢ŽééC}ýýñÄó8¿¨™Îâ‰~_ýiðø¡C/ýÓÀ@ÿì ¿/j80ÀA´»Em¡ËZޏ°]d¥Õ¶…\¡·þ©s5~â¡Ô>  ¢¹SΖÆô'8N_úœ˜czº¯"2°ì v-ÿÕãß"qÇâ¸óµßg‘ Àgv/«Ñê ³û‘ßjZLR5cøÀ¼²b“列¡}JI¦Eú«löVßvƒƒ/½Ô߯PôË®~råʱcÇÏÊ'OÎÍå6ò9¾‚±ÆâZGÀ?ûÍÜìWßœ|/hØw>ç°»G¦ŽÖöBÚl‹¸6Ú;j^ÑÓôÚFDÓC(îØ—{ÄFÍÄ)£Fâ÷Ëdò¾«W¯Ì;vö¸LqöøÜÜz®˜ç¸Êò#¥’ßÿÍììwß|õê«xŽC}ð9€]> l¿ËåEФ§í½ä„ãçúAzmÅfuLÙxwìÇQB*ÔPgí_ù]›FFxÇXʳx릑·ñþMx§äÅà1øxÀÞÉ'xÏF¼©ÖR‹Ã~À^Ü”¸÷/git4ÐÖ5(É.M²“#xã?™ììÙóX#qÈúúŽ8`Ϩ[lû_=Pîp‡ ×ÁXpDd”¶7QŠ;^üBã*yÛ÷¿´'Μ”•wÅ â…Ôeè$^r€½¦ë·Ú¿íª iñö»«£äJaó ðõ>ˉ<^TÚJLd¯hOœ<£ñHòJFRW²ó xÑ©Ûþí9qŠn,îV§×m³+1šæ«Ê ®è‹8\H‡EïJÇ.W‚ á°l©(Ø÷c„åÒ®fÆ;±¿#}P_àæùªJS¬3œÞ»#6âjê¯(|  ·`·6º¼ðã !G³A͵³ûÃ¥¢¡Ô~…·ËW•÷曲9ïFXÑûFß·ôþŠºÛÝx>†ÐkÉŒ}^؈‹:› lvç.Ü—ƒžÍ(¸ZP‡ˆPN—+M§ˆRÈ »ÖŸc6v¶r#,|  ×íNãk§cZ³LŠí.d°wEΩcj§8Cî†D€…T$ …#©BUgk¾²Ã ½Gã£BWtÏm6Út8³¹ÖáXt÷rîÐÕ°pЦ (¤HGÃ.—ÓQû:Ú턽e#Á‹½ÇºC¬Ys/ãŽæ)*4J9SðÒìæó½;qGkŠiëÜ.xq@:1nƒuîMÇ ²Æ6¤¨˜î/ÌͽR¢Üe!! ½H“žM›+t7Äãâÿ‰î°ëŠÝ>ùm‡Í=cç9«bl5ݸ èÍD†«•̃­’Ã*tüQGÖØ6—a Á ²×q‡èf'{o„¸Úpˆ- gI;Úûðw2^…]ÎíÇ+{^Œ½>LÀ¯…Ó¾/ÞÀûÅ:¡¤B¸cP»«UÊ ‘°ÓÑÊÕ»ÂPqI«¨Ëñ‚ãR3 A7.Hh q¶—rh©ÖAÂŒVG8ø|áÁÇ6Ëìbe85MU © F´­ô…mëb |k÷÷‡\NgëÍü­šì é°sÏ"œ›´9\䘀¼Â©º÷„˜Ðë°®öÚõ¤Õjó ËÂQ©£ÛÚPTH¥š·²×N%­Û°x !@ù*[â(ÂFu:{œltk ZxÊO ·Ûêf‹Ï+¯KDÕï_·ë¤ždÆh}qþ6›Ý¹Š®G”Ê‹Q}tE”âKYáÅGì6Á ‰[I®6Q倪HÉö¢ g‚{kp¥TG!Øv~ñܧ©š;¶J¥S©†½uUpÐÒ "νœ#fÛ»õL€ ÁlïvÔÏÚw¶Û8½àŽŽ—®ÛNN˜çw©ÝT ã MÒáªKU‘µªì;öÞBbIMÀaƒÂ8H5êØr:jßYìa‡y~–š–*;¼ à਄ ÛÛŒ0ª=4Îp¤Ï °”%@w´µ«ƒ„° ¼mjZªìÐO.éàØ®gj3ÌEVá Ji¨ŒÀA'çÏXÙN¼-u$‚ÌP¦`‡€€R*ZݰB8š.€,€­ÔmmT¸¿è)»Ûà„]8€6¨[v¦M‰ÖÁ BšQ·ë߬4£~×?Ø.hJÝÞ‚°HДºÂ¸cž  é)ؽ h®væŸ- O Ð ˜Á´CíÒ†ÐP4§.Q U@sjUØõhJ¤®0KMH9aÆ8Ð2\È6‰* UÒõ;DÚ`æ m°®-ÑC¢ …unÙˆU€8[â XjhpضˆU€8Ñ-ÞpB¢ hCà  h¨Ú¡PnÀ`6»ªlö§ðtÛ±¹šú]x2€–bnÉuà™Z£ ‡au`ùÿ5«endstream endobj 67 0 obj << /Length 779 /Filter /FlateDecode >> stream xÚÿü   & (*- ', 200. ('&6/:'2-/S>k>x+U",NG9cM'IrGVN3F8K+2oo3n&5UHR-QR8jmLk-Qp3gjlQI MK> >> endobj 63 0 obj << /Font << /F19 14 0 R /F20 17 0 R >> /XObject << /Im8 62 0 R >> /ProcSet [ /PDF /Text /ImageC /ImageI ] >> endobj 70 0 obj << /Length 1311 /Filter /FlateDecode >> stream xÚµXMsÛ6½ëWðVi¦„ñ ¢7·ãdšiš6VzIsEZæ„¢ŠŠ£ßʼn¤d·îx<ÁÅ.ðÞîÃJ$ÁðG&¡ÂB&Ëõ#š¸™4«$~|ÿzBüEͧ1OÀ&9¼HÍ›”0¤¹RO÷G5A” ‘¤i-©YùuòñNrñfbgYòq‘d=!L!Áu˜¨&·“?;‡° Ž‘Èø¸'· 'ë0´ ˜>ñ”2ªüÞ'HP°Ÿ?Ï'W¯ˆ†#Å!Ìü>a,C$ã<‚€Gœ%óüãôÕŒái±hwM±}š¿™ÜÌ;ß\2¤TF“ØóÛ-a AÕ‹!™¸¡°˜9È;åHc).0’R °á°åáÌ¡a¦—eHr&;˜!¥À½Ê±0ËÄ[( ðu3#Óå,%Ó”ma†KÀ{Õ,eŒOïahçÍœ¥â§S.d†pFiÂuœ e3¿›ÅÊfs—Øî1JìTjÀ…x±_w*…öTÁ®33GûuºÏ ˜øà>{˜ãèØD§DØÔÕø8ð‡-$«EnSW{3bÓm»¨ó™fÓ&w“·0qýö÷`I¨Ê€uݺé²n‹Æ0°X§§1dr8ÓiĤÖ6½×¼oлA :¡Â¯ã0‚ºb‡s«õΰ³3§G§»$L ¹Jbw£ wÖÑP'\ . —qôzã`ûbq6h~3hU¹*ò«zSÃLjf !0Î!fä>ìîÜ` ciùØzJ›@ÐWìʦÈ{çí`Á t 2õÙ¸G Ïâì.à>°s¸÷¢àGœQ5Ý,?{°ËöÁ£Yï¾ðÞC\® _ŒÒXH×.óÊ,Ú¸C–e)Éų‘žC¶³;ìÐ>Î Û>ŒìQôåfWùÜÜm ‡«Q’ôÙÜnŠ .ë7ð†WAƒî=+Ö`lá+—Š$¦i; 8LQ$U&‡Î|zvFݵRŠ´’ú¥¯ê§@æ/\ Ù¥k!öwùZèG?½Ž_›ÄÞ;RÌpg³J*§ðülî*šãŒ— Tž%këgÍj«gµ¿´íG>D—ˆ Nÿ—K‚·óìbI» %¹{JIõ¢”Týf±ô²TÁu™¿\Tñ%lŠä Eî‡ÅE\m0m+ì»YàEnã +ÜåÓ„éö!mU7 ½$—ÿ‚ŠháY*‚Ý*öqŽŠ^ô*âè¿ì €FˆB§´ fýƒ§EDÉTÍÝÖKÙ¾6V¶ˆJ °:TW뛀fgË£¬W±ÊvAèêÄ4ëLbñi‹zv¦l´gçg[vÐ Í#ÇíåqËÞYX1"ž—æ‚ìšt6½·gµàÙ«0¬ÊÖx?Ú­Ã×Ä4U/-˱ß3²Ü™]’å¡}ŽËr?ú©,þð°úÍØ6¶)ÜZ°ßvº€ÑQfü¥Ñ‹ýžA¯3»„ÞÐ>ÇÑëG?Eï(ðÛ¢ õš[À¼6®»*^U¥Át5PoPLëìò/æ#üÛ·‡ÇBp™F>»í¤á[½¯¥``wßuÿŸrÌgwßÑö•ë›~tçùlþ2ïL£{}˜¬Ýµk¯/Þøæö½î¾c×+ÿR»ßíEþ­ˆÛçÒ¾¦ëv\!®5{:‚ÝJ OH~Fx®ŠAÊKE/¡ÛÙų«Þö•x÷>Uÿòxeendstream endobj 69 0 obj << /Type /Page /Contents 70 0 R /Resources 68 0 R /MediaBox [0 0 792 612] /Parent 60 0 R /Annots [ 78 0 R ] >> endobj 78 0 obj << /Type /Annot /Border[0]/H/I/C[1 0 0] /Rect [749.0261 10.4147 756.9963 24.3624] /Subtype /Link /A << /S /GoTo /D (UNDEFINED) >> >> endobj 71 0 obj << /D [69 0 R /XYZ 36 597.056 null] >> endobj 68 0 obj << /Font << /F19 14 0 R /F20 17 0 R /F29 74 0 R /F27 77 0 R >> /ProcSet [ /PDF /Text ] >> endobj 81 0 obj << /Length 2197 /Filter /FlateDecode >> stream xÚµZK“Û6¾Ï¯àú²RU„Á“¼‡Ô¦*ñæé]GÞK’GǪ̀L‰c‰cïÔVþû6žEˆÔd'åóÕ@7¾FèD ÿHÁÊBh‰°(‹Õî #Z¸¿²8Üéã»7WÄ·‘Ô\x2E÷aa¾,CšKyy”ITJU,0Òº¤¦áÇ«_~ÃÅ4|weß²â3<`ĵ"ÅîŠrŽJÎtxS_ý|õ¯Ø!Á1ŠŸïÊ5ÃÅ.ÜÚÐa¿§•ÌÙ&8A‚‚ tùÕòêú¢ ª‘ä ey[P±RBè«b¹þeöþÝó+ËÙr®ÉìPíõœÌªvÛìç¿-¿»úz5 "£B©¢ÿÓv¶O¼°‹è* ))³³€r¤qYð " Ãù™iDK„™äàEMajRA‡ù½K%‰&o”0p/ïÝÍ|P»Æøl{t×Ê\ØìaNõìÐÜÔ›{ÿyÛÞ»O÷ͱ]˜ÖLJ9ØîzZmÅ„¯œìûwÐïÇSß• aEiÁK‚Jc[nÀHQÌü€m H 1,Üc‹RŽ0´EÚ¯ƒAfºaarQÌ`VËS;v¸æì8!g´c£ag¾Æ}Å?V{éÉöãr®øì)ÁÓ{ãqno½—˜”ø±m‚¯TÍší¾uÏáýºj«Ó±1Í ªìq.v@w)Ð (‹+B»$ÒŒªé¶öܱ(gˆÔÄã–"x"´”E”3P«ÝÚµO›×óLòÙ«¶‚‰‹ê¦Z£O}xe€0ŒçL—]ÇêP펾å­¥HhÑ ÓÈ>êÐûí¶Þ¼¾¾¾6ø^ß6ÍõMu0z²:öÕ.šõ¦ª«ÿl7ÇWîù÷sÊ‚ITÊX.6uñ³ dˆ|éøa0%0vl5?Ql*~Òþ¦ãg¨ý4~zŠǸ‰¾3RÍ!žü‹êƒyòÔv´µ? ¥ÖÜûHé$Ö›ÃîÝÍ£§}Ó‹+XÓÞÃv¢Ö*{õº6„È6‡Øsëîm¬í9~1X=${qÿ&ýŽù7ˆMú7cçˆÚþM/?6íy§';ÂÛx\U}–äýÅ >½Î#Hè쪫4‚EÛ%šp͇2·»I&Tøv KDæÝXeŠtŒrfÄèÔJ˜Ë"íîÐ༴àgð9áñÒúf`/M}{öÞ·íÃY¢!kRdmî4©ŒæÞ¬ Z!†•êkùÅIbò+aüÔª¢B«¾=—áÛNá3Tb¬¬›UU›di8û4*1‡(M!4ę̀~õÅyDÔ1Œý!@BÓ)<*ŒMÒ\è‘Sƒ¸T‰Lᥦv—"Ò9¸ûíþøüV®¶I×O6µý÷•©Bl^k¿¯»ôw_ÁË-›A–ƒ›*ˆJŸMI»1îbãÔ‘1âE™c`lž8Rcmút–7΢֣ڙ>D–KÝyo·fÜ‹ºq¥çÿ¸MÓî÷ øÎÜõ7°¤ +‘ÒÈØO`]íÛ´ÁD£»®Ÿân„² ]îj·«¿¹G`êº+¦ß~o$ Oržš183ý|Ç' Gä&Ÿ±ãeéy`ï~NíOìÎc×gèæ3jög #•*O¸0 \2ašsrfõPÄK Á’­PŸì—EÁ(g')ÍL( «–‰d/5TÜ…âƈi_ñ®z8Ž$¸ªÆ“¾ &¸¡mư~‚;P2žà28â…%JMÁ2ìÎft1¡“!¡Ë¦»ŠÂü*ÙÁ'¶Âg¨d<ã-LS51m¢Ô>Ãî>–xûùCØKs¼û/ÒŸ„@ Bj¾}t<¼øøhy›e¥ñ0°úûM$AÎÄ2)K$¡—$\q­& qBžÍØ„ÑÀì™bì(7ÎØiw cíÍ3vÏÞñ”:gs†±‡šóŒÝÓÜ+ÅÃØ+²\nh3ºÚ&m&WX…©Rwå”y•ã|Â9ÒšªK8ŸLq>ÅH·i:ÊùAnšó“Ç8ÿTñ9ÎOwœ6÷!õùìžMÜ=í«Ýv•Û^€Ê„÷ ¼ñBÛŒÙôtÛ¡¯älæÀt8c€E©)À žƒŒ«¾æt;xÛ›†[ƒÔ…î¯Ç ÐnûdþÃ|]ÂóòŸ~CØR×áÓæ›–#­¤x}ù3çò—f ®¦è+ˆ³WÒÙŸB^cóÜ•;N]ƒ3Ì5P›'®Tm–·âÆþ® 5ª¡§5–0“…M0Siޱ˜"&'Æ=/q™ã%ª€,ƒà9Z²B‹Dê+q¸Oµî}½^­ìÉ|<{”þ@㸽©7¹rÒ2Ä)§I¹5Ó¦§1‡*ÐÔ&2Ût§.„ãveÝóå—óçbvÜØÕäÑ.5m<ÿ1‡ÙǰàªÛ\5h~ӣÊ\龂 ’<_ŒþbÃ\ŸýÚ=öÉÖÄbZ÷§.AZêO;3 ë7N{lì¾j‡—ßæüdþm^¼5éñß»—û6ü7^øëŸßú»7QÁ*”úÛýÿ¨Ýå'»WóÉíÙÜt'ÖÞUP§éÒ\"®5+’Ñ^ö›1.J©]„«d¦Z‘t Ý(—b̯æKQ^ny˜7ÿTÆ£Ûendstream endobj 80 0 obj << /Type /Page /Contents 81 0 R /Resources 79 0 R /MediaBox [0 0 792 612] /Parent 60 0 R /Annots [ 89 0 R ] >> endobj 89 0 obj << /Type /Annot /Border[0]/H/I/C[1 0 0] /Rect [749.0261 10.4147 756.9963 24.3624] /Subtype /Link /A << /S /GoTo /D (UNDEFINED) >> >> endobj 82 0 obj << /D [80 0 R /XYZ 36 597.056 null] >> endobj 79 0 obj << /Font << /F19 14 0 R /F20 17 0 R /F29 74 0 R /F22 20 0 R /F27 77 0 R /F38 85 0 R /F39 88 0 R >> /ProcSet [ /PDF /Text ] >> endobj 93 0 obj << /Length 980 /Filter /FlateDecode >> stream xÚ­VMsÛ6½óW`z"gJX|¹¥mšIfÒLM/I´ÄȬ%R‘i;þ÷]$‰´å™x<2ñ±Ü}|û€]Nþq"4QÖP¦4YnF„Ÿ&û5‰§ÿ¼MxÿŽ÷tæmÈa#w;9ÔJcbƒÉóYq€Kª…$gÔZ Îã÷äóWFVèé}âW¹Ç £Òœl°Œ°jXÙ$Ÿ’¿GˆNj*t!÷Þcd; ÃâÄUŠõè”PÔHá¿ø·Erñ'·,.a˜Å7šSe%¥8zdY¬>§ª®\•]™åB›ôCÙ¬7Oëf}]¼OÞ,Æ@J2_‘8ÌOB/Ð+~žØü+®(†Ïê$µLk’ FSrÎY)hÊ„‘˜Füͤ9Gå¡wS`däe=Z8¶o›»ª©«¦C¾…H»Ö=eÚeP¤÷˜´*¯ÃÖÎ-í1­[þñV—YÎSŸ¡ÊuÁÅÖ/`"yÚçÍ¿…÷#?ß{£e»nê®n›Ó낲€H%)×ZÏòB `¢çÅãÍx~Â4:?¹¶H7(d+òز„*¸òl v£™£ì ãægÁýs8Oñ¢O zÀ+{,à=Lc¼–ÍA¸ò‡ MŽPÞ”ÛmJL Tn8ò §ùh5—Åq§î\ôWY.¥L¿p!]üS°NåÆâwö„*¶Û·?pÀ{ó£ZPК÷æ¿å”»]–;}6«jõ¨nÀÐB‚xqÝD~ŸÒÍ`vV738_J7¨ÝÄ(½nêeÛÐÛýæ.0ƒ—œOòšV3±ò:ui§ö7 ^ !ÛîùÿÝÞtnSÝW"½*7îjÉïÜf£?¸ÄѦzT 7öÅÕù}JƒÙYuÌà|)uL NÔ£¼Ü׫uEýi¥7íí~9!×Uu Ÿd7­fkdâ.ÒHS݇ZsíJŠ/%}Yª›•«I^CN;k üN¦wUxóêöòô S¡; C+|Å&ßQˆÅˆ`}ðJSʯ€lH0éýöÅ»­%´Ø¸3ÀÆÁ‡É¥ œâ|£éÃÏï¦qb±ÈçØ-k‹#V9µJAß ¾ç*‡Ú~XYd…LýâÃ&”ë_{ïÐæ_·ðÑ5¯‹Mçé÷ýÁ®7~óéc?z;ð¹ ]¿iÃã/Þ節{^†»=¸ôÛÀ˜dJº Ä }íóZhä+mìp`žÅ«xqhçØíbŽÕžQúùȇ#ô?¡C¥endstream endobj 92 0 obj << /Type /Page /Contents 93 0 R /Resources 91 0 R /MediaBox [0 0 792 612] /Parent 60 0 R /Annots [ 95 0 R ] >> endobj 90 0 obj << /Type /XObject /Subtype /Image /Width 430 /Height 354 /BitsPerComponent 8 /ColorSpace [/Indexed /DeviceRGB 249 96 0 R] /Length 5392 /Filter /FlateDecode >> stream xÚíy€EÆßb¼ñ>ãŠë‰(Šâ‰ш·ˆˆ«ñ•˜‰àŠFTãAT4"E5x"‰ ½â.†,ê‚IÿçÌìtwÝ]=}TM³3Ó]ÓÙ©ßV}¯ê½×µ`ŠCegRI>‹RtIiïdZ @«ZX)}§~½¶$ŸE)º¤´7h9H+AÕ ìÕZú3h¡Xc 2´`VjãÏB 6a96žùq‘¬Œê¬óã Z( Z e/-ÎÂÈI‹%|Í>ͽ ¶ÂðTôVzZ ´ØcùhÑH­õqÐ*›ÖN¹r´ØŸÑ3K’EZ™hEmmNk½TKì[áëÁÿi ûÜp¨-žVØÖÓZÏ«V8îÅOº‘¥Õ¬þŵ_Ôƒª¡Å€1ƒÁ“ÒÊß2´6ÖKKòZë%‘­òFBn^ý¯1hH+ü´rÑ Ûºp žkþèÕ {–…ó-b©D]*•Å“µæZÕÒŠ pÖz®ë¨¦g•°à«¢…R-É~-{iÉÇA ´PJ¦5 €ÒF!8l´À²bŽó†;£QF‰æÌÛÞsKö>ŠÕeÁ¶´\ ÉÚÃzZŒ±öp‘Vk„2ò”DÑÒjùË qÑÂÕÑj#\°5xtŠ+ ŸHk$X}Z…­DZ£Áê¡*h%Ñbaõ=Yƒ§¹G6Z ,"ö ´ §ÅÃêÿÐ’Rf{ 8hU@+¢õ­¨«Í QZCf½£èÍàY®Z#ÑŠ1±“úV‡?`Â'~l Êu@« ZRwÓÓ"‘u¸ÁQI‹@«Z$Ñ’pii‰ƒ]‡;ª: 5jßR¾Õëßw:3ô©ê€V^›POË7£ÅJ1£¤ªhÐ’æ[üH847G¸í9+P0‡ÔK´Ìhå[ËÈ5«ÒÏÉ@Ë2Z”4-ûúÖ2@k¼iõ݉#^0@)­´Zøóµ¹H´ZãW¢ïêv T´Æî2ˆÕõïZ Z 5 --whÑTZ®|Kš-g¾ehh•ñ-›C+O¨uV†Š–Úõ-Ÿw‰V®Pû,x3Z\DŠC´òE€Z8;ÖТøˆß* cÿ^]¤•)Ô‰µ !ØNù­â7vÓbbÕ´äP²¬èh)Â"Õß*~Au~½,´´}+C¨EOKÕ·(•–ß#-õ[²˜e %+i勵ÖT-J¥Uó—3¡•+Ô:ZSZ~dó©¾U8h8DËÙÂÑš’i\ ´*¦ä§UóÀÑ@ZÁ(}Ë­Ji>h9C+ðAËoéƒhYEËéñ2iu*,¹hÝÖYø´…ŽÒ*X·:Õu¬\´6ì}ì¦-qfZ$½¨]·ˆ_‰&Á‹ÂŸ!¡v‡]ñ&ËhÜôé>½ßësÛ¶]ó^%-æ¡V;-Y·,zÇ*œ¡ãW60§õ–Þ0x^ÿ¯èþ½±ðÈZ \¤|Y¯nÉî}•_ȼ\ÃcÈ•b!­£BZÔ mú–´DÝ’h‘Ê—¬§U•ËÜœÖ{´6ß½÷›=µG먴‘ÐFZZÝJz(hÉMXv÷2§uFß¼8k_z¦mÛÎ:ÕEZjÝò‹ åÛfe\àæ®Í›®Ù¶íÒý®ÒÑ"-- ¬ ¥n'­Ýg¾qóÜtkó«µ?´.H9 ¶€–J·|>Š&…sØê‘°7=>p°–qÁ«ó®dÔ;;Vé–ï3¦©c…Äù–dZheôpùyÅ _xÈG®ÏÝx5¯<5ip <&h¹äßjü[VÒú×e—ýñ'¿ºÃÙ¾U°nYî;þËŸùÙ‡=ºåD\Æ?¾ö£/ýþ¤“¾1ST[–o"–©[VÓÚzö?¿üõoorrÝ%ëv)æQ›tUL¿’t˱bJë’?½ø?œœüùÖu7­ÓÒJñW¼´‘ [Ž–Œ´¾¿{ò½ÖLN¾#fÎÀDëNá+Ÿm ÅéÖxÓê•5û÷i½T!?‘2ÐÝ£õ8Õ -»[L‹×­ñ§õ˜{­yÙädZdEßj5‰Ömkö¿kŸÖ¿v\qšißÒËV•#a“tkÇÖ‹-Y’F‹Dï¤VF“tË_³ÿ×39ùº%K^"EѨ¼’ÌsM®äÆêÖ-ùèÁ÷^sÌãžòto¤~ÞUùì¸Iºµ¤W-úÀêÙâ––*_yjÔ|k¬ü[ å’ ´\òo–Kþ­1§uãaw{ü³>üA÷;ºe=­;=ò }zÞñ¯}Ð]ÞݲŸÖCîñ؉AyÃûæ;â:Π[Lx§ÎkKÒÚëÚ¿÷Q=üU‡?x¶VåOŽ3è{ûc5„S¶Ò:è=¯?þè×¾í­ûÜó´ë>dæ&®/S2]·ø›U3·ªfî̬÷>ŒûÖç?Ä)ÇÍþtâÆß\yÊ…óå-^, {ÍÜ¢.Õ¸L˜A·÷_%1ï.ìHŠ;‚ÖÜDzÑzÁÍÛoÙþï«>>1A¿˜½ö›‡Ý&ô1‹5â“’T[\FØkæn˜kNËê(šGÝ|ÃùÞcb~Oº:'<ÿÕÒ8§õiYDKOÈÑ"îܤ¼3mMC¡ ­wæ«·¼ýŽ+Î8aáÄÄË_³×ÇVd¥Ed-E<¡úÖΊ[Ãs7¯¶›Ösn¸áºÓƒÿM\¿a÷î ‡®üf‘–8,ÖJKŸJ‹Ü£|øÊí‡ü÷ЉÎî¾øÑ»úë‘iÕb¶Ô´D¼iHüݬ‰?e1­=.ºùº½g¯˜ ‰³«¯W„¨B!c’* ° ¾1qð­S.|Òÿfvð;OîlÈç+¶`vÜœ8øàª}çŸðÅøy=ûµ¯<5Ὰãêß-—ü[ å’ ´\òo=­öÕí ÛK—B·\ ÕnwÙt{zY»VF–dsu«½ü+—ž?=ý‡ö·Úºö‹ŸMgS$̪uT ¯ÛXÝÚuõÒé¹ÒnÿU^xbiq) såøsE݇­±ºõÉötTÚêù0±Œ¸T;!{AÎpå–38Zº•H«ÝûÖòv[Z%L¢Eìv59“ -ÒÓ2^¹OÓ-Îw?\pÚùm ¹ZnQhJkÙ¥a×ZÚVê?þi^É´˜ˆâiiîOè³·Õåw¨ö…—b­Êü'¦#á¥ñ@ØV-ÂìÍ5´Ä»%“L‹»Z¡´”÷Õõe·¾ÞůÙÎÚ‘ð¦vûüaךþ¾ÊH ~KHWd¸r¶žö)#ö-Y·HåÖ×ÑRÕ"Ki=ÝZ>=}ÑôEËÛRr´“5®Ä^E€V˜•¡˜o‘fÇwu‡ÒßÊÕ¾Ùñ²ÞÌxzéæöRå|•¸fW÷¯Ø9)f¸Jó-Ítk4Zª¸ŒDZÉo«Ü¶:ÇÊS»½¹=Ý.hI¢Šß ÷ƒ£Õcœ÷Dò![iÁ¾sSÝð#¬<Ì·¬Ûþ-Ђ ´ªõou¬+æ´üanä}OwÿVÇý¾å~ndfÝZ…çFV`HæÓ­1 UTn$å«‘/X4§n¹N‹Í<Ãx¢+-ãʈ[·H¨Cüz•¶1³Ÿ0¨‘£oE¹‘š*[näÜéø6¼¿lO±—¬Zº¥¥EÚÉåEAÅÓŠr#S¼ôɹ‘| qÍ—só¶ø>ªnE´(|bvsb<‘~èªä˜cW&sÊ·¾7˜ˆçÊ\¤Ò Ri)Vébú[I´LtK¢S63s‚Øš>»6ŽŸcTá?BÓ:îÃs¹‘é0ɹ‘µÒ2Ò-Šò’#jZ¾ŠÛÆäó—`:–X½ ZÏäFþ70¥¥ë¡•G·J¢Å½+šVpÜ•ÛÙ”GKŽé(ÃÊE·X*Eõ-|¡´ö¸èæÝIóÖ¬¹‘C;‘#G ”dæÕ-âtËœUN+häeL½y«†H³ã|ó-"Þ°b:xh\-É&”>åg†UÒ|ún3I5´°ŠYyª~0M•j¦5þ­"Wž’íó̰àßÇuÂU.—Æù·Vuã²ñÜ^ÙØu¦¬Ê©[îúŽZ'.^¹òÌÓÞ¢³´Æ?.#¦uèê™ÛwíºcÇ™‹m¥Ó7õ´š¿ÒºüÄÕ·Þ~ëÌÌ­wüVj£ð{ ~æ[°«=ŸLÇ¢f%^yUãò·BZ箘ٵõ—ïZ´víÆì´(úÙÍNŒ¨›ÉçV•wœèÕN–ŒhHëÍçìšùå¼yóö¼¼«£Å<NÑÏaµøY<]Ž­Ö{ÎชhZ¹u+sû'UTœËÎÕ”ÖßvÍ,š·u±¦/ð´æšs®­Õ§Hú3|ƵH¸òðCŠHŠ—©º•%72ÊûáÒ‡¢xá.¯BþÏeí1÷Ó‹/\¨72¤µpíí3ïš·x£ZÛåv Û–ºÊS¤ ÌÐRÔèr0-Y·2æFúŠÛî ™¬$ÜoÒOøŠ›Šš¤™ÒZyñ­k÷ÔêŒ ´$Ý2Ê$ÖUÏ&)’Q´b³˜Ë Ï=ᜋp¹!­DRÕÓ’tË$7’óÓ¢*h]¾xÅ6vÓhQy´ˆ³>e3#ÕÊPÍ·Ìr#}î3yûë9)‹V·ûÊn7™V76Bc€9/Ã!"îcfK@hl*ï«›%7R¸ÑBZÜ9_uóåâ× ëY‘Pþq䙫t+=7R¸aûN|X´ y­šûœïæ™[K«›HK +uåÉd¾e:Á-=;Ï^Z%­êš¬:K«yþ-‡× ˜w<¹‘ˆË@ßB\h5>ï¸IºZ®é“cÂÄp’² 3µsZˆ(VnÝâRWG¢•\».V.Ýâò8Z|ô;ãsá–ÓÓ&™SÄ\,-±ºeB‹T“ÜR½IÚ¤£B>håÑ-}ßReu±!íd–Ú¥Ë4‚nù¦#!–CºU-MV t+Õ&L¢E¢n™Ó"Ð*x¾y&$Û„ª ɤ´IRƒÉ·InY¶–Z­fÇX'„ ´lÐ-øŽÒ-Äe¸¤[ å’n–Kº•7T°Ì BèVá- Z¥êVÆ}#U¯Ö0I²4`Ð-³ÜHm¾c”ƒZ¥ê–ᾑiÉ] ËÕ-£}#A«îù–An$hÕ?ßʾodzâ$t«lÝʶo¤dú‚ñZuη°¡Kë„ …uBЂ qè[ˆË-Äe ž´\Ò-/±é<æ§ú\Zñ2Bò [Ùiyê†-‡–Z9uËó½^Cyž7xøÃ'ÓàgxØ‹Þyì‡ã×qå°Vø6:äyÒ£º•HkÐèQßò¸NæqýÎãk{Lm/~_,ªås]Xºàãð t+e$ô¸TÒò}‘û,\A¨å«h±WðÙºU-Ïó²ÓŠ+‡#¡Ô…˜ 6ž–‰néiy|;õ-Ÿ¯å±æ„\µÉ´ŒtË×ëV"-/-/3-èVvZqë16!gz‘…ÇÚ„¬¹È2÷<Þ&ä­Cþ ï5Ú&aÐË8ÙÂZ† ë„ …uBЂ«qºß1â2зJÑ-ÐrI·@ËÝ"-'t«ôÔÐE·Ä¸v.eŽÏ˜-tK±yé6~­ºu+ëÆt U¿nÉI‘ e±n¡o¹£[šG‰A·ØHÉ FÐÂ:!haë„ ÿü[eÄe o!.´—Ñ8Ý-·tK½i» I¸Ï‰úcݘ ´òé–Ÿ1Í.#-ý Êìf+#§nÉHÊ–LÈveA¨5‹”´ [¹t‹„Ö÷g6”$a<‹œ•Bî^Ü®’ìÞ“ Uô:!ߢ‰í[Á º•Øþ$ U̧¡[X'„ þ-ûã2з—Ý­1Ó-ÐrN·()^LZXÉ΂ÝÊC‹R?M‰¯@«ÝŠ3£dGŠ]"ìz;ÅIqÔçTx—V!º¥\½3KøV‰çJJ´ˆMb­t‹ü„ü-‰Ó¨ü9%­¨‹a$,H·Riq~Ÿ_™ÏF‹@« Ý"?–˜ÌÕ–i‘¯é[ Ò-‰C‹HQ´FÔ-¹o1æ q3ÄŽ„ÄdHJ´ÂP5xÐ-ï8ˌʤ¡GsÑ@·ª¤5ªÇº…uBø·@«bÝ‚ïqè[#éV‹/SI¥Ugnõiñ•§ºÚ²jªÚOøt«O‹l¦º«´eªRËCøÅZtKI«Lõ­úuKÑ·¦Ôв@·8ƒkn$Tõ¬šhQ³GBnKKÙ»j¢EÐ-‰1´¬Ò­ðƒnÅ´ˆ¥e•n…¿tKGË*Ýj4-n‘½ºEЭ$+Ã2ÝÂ|+Ù‚·J·0ßÂÊÖ ë¡µ _£[~vI§N‰DkS—ÁW¶ÈWœø=Z!§ý‚ûeØï;îcZ0÷÷Ëp€VÃæ[Ž•fÏ·- o–KºZ.éhA·@ ºÝOZŽY·‚Àñ?ÀfÐÝr¿ht‹HøŠc@k|Š [455ºµ%žvàÑZÑ|‹„\ñ(iù£1´b"4t1F¸†©Í&õ­X·b‹éx.ô®F„ãm‚hÕKËí¢¤Œ]‰hßZŠÅ´¦ÕB±ºð´P,/,­š¢=RJIjöàU-¬L´t%ù,JÑ%¥½AË5ZÉâ}¯Ö˜@ŒOù?$µ-uendstream endobj 96 0 obj << /Length 761 /Filter /FlateDecode >> stream xÚîý   - 5 , '=+ "!F!,)'ÿ'!&$%-#$45,*)-),&3-6(930@(A;8“6=869;62ž ¹@4JµX,MIB*DLF:6HB V2W{(1PHh5‚Q€PŠefdbfwhw {q²JL`p`°JVQ‹[q{he™`u]—P“Vu„to\‰[mnwdsytsZx!jzetrvorŠvumq|kŠªt¥Í€5¤Ô§É¨¿’…{|‰ £ÆÙiÆuqt„ˆ‚¦Œ£“–™‚Œ“†Š™„“{Œ[§ŠŒ‘}DªÂG´“¢£““‘\­¢—™–Ñ•’œ£Qµâª²k¼x鑜ž•™ÎO¿×xÀiù“ôYÄÏ §¼¥¨©ý›f¿æ²º>£¬´×«.»¥¢þ¢µÆ ª²›¨ªÓÓŸžÔç¶Á{Æåºµ´¢¾Èı³ÈÀ^ÌͶº³ÆÊ5´¸Çÿ±%ö½ÀÐ.ßÌ̶²ÓÔ¼¿ºº¸çú¹KÏÖ(æÒ½ÂÉÏÜ—ÕðÑÆ¦ëØ’Ûê×ÃÅñÛÆÅ÷ÊÌÉøÛÉÍÏÄÑÑäèÌÐÊÝä1ÿݯÝñÍÌþúãÿçÇÚãèôüëíì$ØÚÖî÷×ÞÓ½çóÃäôõ÷ ôüûûëÞÞâäáÝèéñë®ÛêñêìéâñäÚóøíôéôöòíøûöôøõüððýþøû÷þÿüôð\ªendstream endobj 95 0 obj << /Type /Annot /Border[0]/H/I/C[1 0 0] /Rect [749.0261 10.4147 756.9963 24.3624] /Subtype /Link /A << /S /GoTo /D (UNDEFINED) >> >> endobj 94 0 obj << /D [92 0 R /XYZ 36 597.056 null] >> endobj 91 0 obj << /Font << /F19 14 0 R /F20 17 0 R /F29 74 0 R /F22 20 0 R >> /XObject << /Im9 90 0 R >> /ProcSet [ /PDF /Text /ImageC /ImageI ] >> endobj 99 0 obj << /Length 2013 /Filter /FlateDecode >> stream xÚµYYoÛF~ׯ࣠”ë½´(é4mZ;}Ió K´Í„UŠJbíoïìAjyÈ”S†,³³³ß|óí!’`ø# “‰0 a!“åz†MüG&ÕMßþöýŒ„6ŠÚokž€Mrx‘Ú7)aÈp¥N÷Ç0ADq¤#©mùçìí;œ¬ ‹—3÷”%Ÿà#n4IÖ3B’‹æI1»˜ýÚz„(¸DLj~Ü—o‡“usé[°ž«”1¢L řٳËÙùwÄ$ÔÀ#èæò:a\#Ö‰˜Ž ¯5öˆã Ÿql:ýæ; ãóí™/ΨžW–ŒÍËkÿýòâ)¼zõz†qà‰ñ5¥’Q¤±‡‘°-P…øçì,%ólåëK…ò „ˆL÷âÓm¾¼õon÷Wð" Ø7dó¥õVn6ðÆ9^ÖíûÞˆ¥FXC9…š3ÒŒŽj³†*–Êñ£ÕSE½0€Cä×ã` ?#ÂáÐØµfŒ?0Qý85öÀŽÅ9ÈBc<Ò;¶½S"œ~ÜíøÍ.à»,×¶õÂ"¸J‹|“y`!4aß,l2v_T-Å>Ù!o¥ãœo±†7.îåõ“þà,((7b m@zŒq5O¸a%dÂ_Ä9 T„vÌ€fbL0¨8 ­akgÁ@ƒâј«$vw,N^Ôƒä…p¸´iÆKã´µ†xÓ]½Ø¬Õª7…¢ÔØÈѸ•ÛXôÞ!ÍÐíýÉYÊ9Ÿ_Ô–†gdzàuUuxÑÔo^‡òuA·2¬ àÆUñ‡ÐÕÇœÁ.„ÎÑn±ÌyÂ`$ÓÝáÃz ù¸”nã”´m‡ ÀÃ(”a'¾^e×’}QÃÈLȨ¾?7ŠT i+€ %ôT€R ÚDMV@c7Q‘»ÿ§ñ©€8ÞÔ¦•ƒð*æßÀÕ·ƒZP$f|‡Zh¬FâèÖÂÀ¯…²Š8P?pµejߣ€Õ2˜¹BÐó凸xò¯«Íå&ÝÙ*9”éV4^Ôy¹¤²Í¸ˆhÆN©¨á½”jì&(5ÇãRjïJÅñ:J]çzÇ){¹Y¬³±¨"¥J߃§]A6V#Ñth0t‰ì(+’º±¤“Õ%‰›Ø³óÉÁ8â”›‡“#jx/9» rŒÄñ¸äÄ{„q¼é‡ìnˆñy]TÛeº¯ŠoÓ]¶¬²zHXÍØ5Ìqh-O‚ÑH\]šôE,¹É?jÜî!ïWžn9ìVu[G€2·Â–Ç^D¼¡Û;윸„¥]XÆ­"GŸ­U‘»‰:¯‹»ãDù“RЇ)jx/‘» "Äñ¸DÄ{„Hq¼é¶*#‘Yü„Êų«2ƒhލLMàKêÞï­`‚±;Ë‘ xòvŽ$V^¢¥›çN`/EÖ-ãêQ­µ?à LG¥¿?8Fý~-% ÂäcïרæHh­&ök­ÙÔ~-ö7½_öÞ߯u:~ñf§"질„U¥$½ÞO£²¶ Rº5ŒžÐòT¿í¥=1’áˆqI}AcD¨TIcfÇù~ñqák#}¿Ë»÷vfFím8jñFíNÈÝþA·%@ Ä„`Íàt_Åoëzûäüüº,ÑÕ¢BeuóDpFɹWwoEc²…f#D¦ ¸eðhU¶ÄUö¸J=:oaÿ¬l@&xÛ˜Mò6òwo½xw »Á;•,Öëæ0¡ö“ˆ½,`ÑlþUØ î·NLîš)Ê®p+·®¹kµú©`²Á¤;Šùš¶#ƒêHù°;´çEžmê×AÏǯ¶ù8ºm­Õ¬\bÄ(иAyõþpìå´{ ‚R˜ãô—ÀÃ94eXþŸ!“ ÑØÙQo÷Wnb+ô™Ó»ùö÷œ¼}ç†)7T¾Û›»«¿üBÈ_ü}¬Bˆ†½8Vâñˆ=ß@k× ßÔYu½Xf#ãoFi‘G=üaõ¼Ül²¥›­u•Ýä;pi!= UßV姀߈ñ‹ÏËlk}휧=ï~Êú{D6Á ÔI›‰s~X$(ýå'ÒÔ_1lŽŸH·îDº9Ë„mU(§p¢¹,7vÁs³wæQrÛ«ÌIXæÍ^¨îŽ7+{ÆÅcO‘Û{fÆjj òø0èº?Ľ>w[ ßŽ4ò‡óç1<¬Â.¶µp—ZlWMF®mö…?¼Ñ¸üí§ð+B厯]6Š6U#ôƒÒ2FOÿzxâ¡¡ý™(òÙ‚A‚6\ ŒWíié?ŠË3ÍýÖý®ðgéa2ül~·~±»±§‡‡Pðž¡à Æ/.~ Wß·8†ZBç››ðÒø¯ŸÝO&37#_¹óטôc3ÀŽ+Ø}v:‚mK{\*•iH~®ŠAíHE§ÐmíbŒåù |%’ûÀû¬o™4endstream endobj 98 0 obj << /Type /Page /Contents 99 0 R /Resources 97 0 R /MediaBox [0 0 792 612] /Parent 60 0 R /Annots [ 101 0 R ] >> endobj 101 0 obj << /Type /Annot /Border[0]/H/I/C[1 0 0] /Rect [749.0261 10.4147 756.9963 24.3624] /Subtype /Link /A << /S /GoTo /D (UNDEFINED) >> >> endobj 100 0 obj << /D [98 0 R /XYZ 36 597.056 null] >> endobj 97 0 obj << /Font << /F19 14 0 R /F22 20 0 R /F20 17 0 R /F29 74 0 R /F27 77 0 R /F38 85 0 R >> /ProcSet [ /PDF /Text ] >> endobj 104 0 obj << /Length 1174 /Filter /FlateDecode >> stream xÚ­WMÛ6½ûWðV¨~Šâ©hÚ4è¢iЬÓ’[k •%×’7È¿ïðKæZ²¼M…W"9œ½÷fDQDà"ž!©&2C«ÝŒ`†Ü/C‡ Їï^Ϩߣ˜¹s6è´š•”r¬…RÏ÷ǩ’3R‚µÎ˜ÙùÏìÃ'‚Öânfg9ú‚…Î)ÚÍr5—WX(*Pìñÿe™ä0{+üÒ# 63š²ÍÀÉ20£,ScFl„¸ –aÂDyŽ3Á³[ÐxW¹¢[ä-”Eõ¯í×yʹH>Ï™NåzSüpŽr–c’3†„dX©œ&‡sF¸OÎêRY}öRuÃHªi¦á™™„”#¿.e5N¥M9Øõf&ó %|¿·§Þ¢¯©6ÔÀr}ê„«ÆŠ~w4Ü”«`lxð|ÚšQI¿T¹iWmÀ]íËe ká¶nŒ»ïºÐ›êÑ›5' L¶È& fXREoÎhäwŠÑ`v•Ñ‘<'D0~³4õðµ×hwæLÙŸ'EâxržôLE•d˜´”úÎèÙ;{,8ÐÀÁˆkfšüL7—𳫧a =¸L#ŸýcS¬¥dþ NOmõßîùó\$ŠÊuÿþlþ4oç ØO“õéßì½ñ«û·þîuÀ…Poü¢v—ßí›áѽ!¢ÓƒW2#D°ðý®5>‚ýN Ÿïpˆ ’~®ŠC©dŠ]C··‹1V/é+ù_¸÷ÿ—Çß¾endstream endobj 103 0 obj << /Type /Page /Contents 104 0 R /Resources 102 0 R /MediaBox [0 0 792 612] /Parent 107 0 R /Annots [ 106 0 R ] >> endobj 106 0 obj << /Type /Annot /Border[0]/H/I/C[1 0 0] /Rect [749.0261 10.4147 756.9963 24.3624] /Subtype /Link /A << /S /GoTo /D (UNDEFINED) >> >> endobj 105 0 obj << /D [103 0 R /XYZ 36 597.056 null] >> endobj 102 0 obj << /Font << /F19 14 0 R /F20 17 0 R /F29 74 0 R >> /ProcSet [ /PDF /Text ] >> endobj 110 0 obj << /Length 918 /Filter /FlateDecode >> stream xÚ­V[OÛ0}ϯðc"-®ï^“Û Û€(-TkÖ”¡ýû}¶“ÔmÒË4TUŽíãïr|üÙøQÄ’Vc"L‚ …æO(î^&´Z£™k-'r7“SŽ­Ðz{œXÌ¥5('ØZÅÜÊ_ÉÍAàâ<ñ£½A‡`a EÓ„Š™!¢™$×Éׯ"D!–Fl6–4­?ýn×,å\È*8)(– `óc?éP‹˜ÅZ€›þq©0§F#))X$õoÒ³2£iù:,³»þyrÜo °«„@±Õÿ‹”àÖö~4æËa°šQÕ¹ëL`K”rpl•Ò] ȶ[6Laµ€M3H᪡ôÖµÑÔS¬P…ОÜ/3iQ–YNÓñ=f9ç"}qó¢¢é´ #)ü¤ëóôµôÓ뻣 &†1$$ÃZÖ™6Œð*%¯jíÕÝ=t#¡çÊSLB¢‘ݨETƒ~¤O´Æ50—í-¡z=NCs]q¶h®ÁÞ‰óΨôJ¶dÕñÉx>|˘Nï'ŽÐr#Y°B‚0ß›¬Èî6²jØN²:âÜBVË{‹¬ØñÕpêU,*)>;Åy….òò%ËA{Ã,„(tK$Y–¹2%f%Z,*ºH`nÙ™«—vÄÎâØ[.\£ñdxÐëõ0Æë!qI°ášoÝé¼Aí¢®m.윤Y.„H¿]]¸â¹QnPï(³òÝåÙÝ&·¶Snqn‘[Ë{Kn±c_} ›W£PÛ¦÷3èx• [{¨¶g à+ ÒaneÚ 0|ÄäQ&«u\3¬8ÓËøuÌ^lp.‹–Ò(—X¡Qln#y ºÃû {.]¡€æïÅ¢ ×A1 íóëƒWxØ ­ÉÀÉW þïE ·RTãvPÔÇ6ŠZÞ7P{¿ž„‹S§o½™áéÑe`Ê‹¬˜9½yãbvØ!:.Üa§Ew=Zx¾qW8£»_‘®©ÿ~vÙé‡rx„pkÍJæ[)Yõº¨.åO÷á@ÓŸ¡H÷3#R?øgÎÙ‡ÀÁ`¾»ÏŽ £åàlÑœÌâ¥_®¾NžDwQŒgOÕ¤ Í¥§÷w¸SàöÐõa÷ÓŒÛâZ¸“k9вÝónáa©´­Ëæ^¼jåXi¶‹Ýslz­ðµTûG^ ç/ÍËŸ4endstream endobj 109 0 obj << /Type /Page /Contents 110 0 R /Resources 108 0 R /MediaBox [0 0 792 612] /Parent 107 0 R /Annots [ 112 0 R ] >> endobj 112 0 obj << /Type /Annot /Border[0]/H/I/C[1 0 0] /Rect [749.0261 10.4147 756.9963 24.3624] /Subtype /Link /A << /S /GoTo /D (UNDEFINED) >> >> endobj 111 0 obj << /D [109 0 R /XYZ 36 597.056 null] >> endobj 108 0 obj << /Font << /F19 14 0 R /F20 17 0 R /F29 74 0 R /F22 20 0 R /F27 77 0 R >> /ProcSet [ /PDF /Text ] >> endobj 115 0 obj << /Length 798 /Filter /FlateDecode >> stream xÚ­•KSÛ0€ïþ:Ú3µÐûÑ[™fZÚ’öLbˆ§±C‡6ÿ¾+Kv 1 2L°¤]íJß>D?ЏBÒjL¤B“2"˜!ÿShyúÓo§ {4s_§Ž@m©“¤”c+´~½=FÖÔJ”l­bnç¯èꆠ)¸8šUŽ~Ä`a EeÄ©ÁRۮ̣ËèkgN!–F¼lÊo#¨l‡ÍnŸYJWápRP,h‚ÍãqttB-bknÆwˆ •†±”,ƒÆÓ«ø$á$^×ëež¤\éxTͲj’—yU¯’›ñy4w¾¤˜ÁPßÓÿžS ˜„~;´é6Z v3ª3 l‰RpPRJ)QÐpæá\b ®9DÒ¬WwH20¯¦Ž;‚†nˆI˜‰«U’Ò¸¸Mh—!§ýòlíR~õ"K(^Ê xßšeÏî>–­ÚA–çÜÃrÇû˾ãã¼®C‘7Ì‹Õ:sbÛ ª)°®‹Eå5¡9üž“™NæEcõ‚:tˆ¬é/!¡Ù¸ÿ¼ªï6º¦}‹N0k;K¯ë@Úô­n%O ;wNaQ>ç ͺ,·Ö~ŽÝ§ý5Òí´s0™ölv¼)¶R²Ð‘[‡ûSwŸþãÄ„[mæÍ5–Ìèüp  Óñ‡íbUwxAytyF§ƒIK³¨îƒÐúÏçP%MµÜ&)Øï§> endobj 117 0 obj << /Type /Annot /Border[0]/H/I/C[1 0 0] /Rect [749.0261 10.4147 756.9963 24.3624] /Subtype /Link /A << /S /GoTo /D (UNDEFINED) >> >> endobj 116 0 obj << /D [114 0 R /XYZ 36 597.056 null] >> endobj 113 0 obj << /Font << /F19 14 0 R /F20 17 0 R /F29 74 0 R >> /ProcSet [ /PDF /Text ] >> endobj 23 0 obj [6 0 R /Fit] endobj 87 0 obj << /Length1 809 /Length2 1997 /Length3 532 /Length 2602 /Filter /FlateDecode >> stream xÚíWy<ÔyOc)¹bi­;ÌA#g®!’Ö}$ǘùa4f˜cÆ=Z%aG…„ä–ûê«ÈÑuU®cX©6ô »íóìîóÏózþ{^Ïï÷ýŸãýý¼?Ç¿ŸÂ!{uc,Ñ4'(ê(\°F“œˆöôÀ $‘qD ‡Ã¡p -ˆ‚‚) DS8Jšê4í÷‹³twÖ¶Lã8:Y“ѾÄ6 Û`q à úâØvTK‚@ùM üj£þ^ùo´¾!ºž±ÏU®òÍ`ã vbõKÿàqã #V/B™uTE€õårÇõk¦Œêø×Å7\Ü©´ß²átj‡Šá bïÊ% L,ßtLö#,›÷):Ö„¼º\«pC:£,S¯(ÄQ£`ÌÈ<ä#‡Y£gµ¸‚ºøV«Uáík7(±ø™Ÿ¥éÆâ÷K­ Ò²E­²úX©™­1=I½ÊMç‡TÌŒÃEΧ¸GH2>Í —­Zy^üØS˜’Îb]›tDìýø¤}0¢¯âÉø<Ã!yHj&ÚSaäØþšpžáëF&YûÒ¯û%A£? ‘«hô Q²J{¡qþ Å“_ƒ¹Ç7ø¾<–ØÊÓy`Ìûì}ãG¼%w–a„èɛSBÀÄ&Q¥(ˆOM`Uà]Û—·óñ²òZݤ%áe~æÊéhrÕwÑQï$Ròš¾ÈöF+Ú KzQ\nÎ,š¹W×ÅÔÂÑ¢6’ް¢ç{ß·k,bäøodVÐrÏ÷ísÛ5¦`cÊ m+šÖ–ˆ …²é (žW“ÞÜu.÷™#¾g:- <Ÿ«Š?¥ñx©£®åã$¾$Y«LÓ½šÉJ„·Tm¼ÝÿN0+ž“ YʺQF¯lÌ¡8FeYnîô¾ý¾4ŸºkÆÄ .l)þ¢~†¿æÍôþ)[Ÿ«÷ªg¼TöñNW0[M²Ša¤ž%a]¯úeVw– ´¤ ïÙÛã;[ÒHÕ¸gõÈ».O_æôu!ZCK–Gm÷«4Ú4µ”ð7 ÜXìfÓhl”jbV”ˆÐºä¾WûwE¼Ò.J«‘zv°U~I|Oy`µ ,_æ¤.Q˜ÿɪÊ}ËÚiL²æ€XÀûÑÐeg¢„°ÂºJ÷ÕN_-þá-C‡cÝåáw•¹ž¤H>Ô[¾Ð0øËjš"¯´Ht‘mà´JáJ²Û³È%¤½¡vô®â–á‹úÀ§6aÙIõ¦–˜Pr›ÚùÕybÎn;?4¿ç„e|_o¡"ïnNºÈ|¶2¹¥›í’ =œR®â_çÑœ« ¥©YœAIÆ Ö 2=í£ L‰5gOV;f¬Ù=˜7p/[˜á£#îXa!2´ƒt®ª*ÛÏT!™Õ#rÑ] ù™ m‚[g!dŠúF€];úLÝ[Uk„ºçάڷ'ÂÍÒYß›°‚Í‹ZÁO<Žj¾yCrT³c-Z5vù9åýmÕBÅü¨ÜÝñ&ÒJuMvM–ýé<×çv+Ë‹r¹é]ó&-Š©R·`©ŽWšNÌ×™·uï#žQùÙ¶rL—«Â(n‘o¤ jS•ã¾²•Ù6ð4þp¿©Å9‰ÚVË칎š~óú¾‘»¡…)IwÍÅlE&Xâ͵„üú¼OÒÔ­è÷6¦Æ(מ‹ ½UP…g>Ër^]ª­ŠÎ # ~u}S^—“ ÞZ’ÚÀ̵Y) ½«K\P}:ª¹x+ùp•£¥ê=!-ðÕîÛ©³@«Îͳú Œ—ó\–b¢Jï“ Ïå\’¿tgÃ2¶})rR(T³Ïþ_ÿoð?±r>≜„³dòMvendstream endobj 88 0 obj << /Type /Font /Subtype /Type1 /Encoding 118 0 R /FirstChar 167 /LastChar 169 /Widths 119 0 R /BaseFont /RVHLNQ+MarVoSym /FontDescriptor 86 0 R >> endobj 86 0 obj << /Ascent 757 /CapHeight 1004 /Descent -9 /FontName /RVHLNQ+MarVoSym /ItalicAngle 0 /StemV 6 /XHeight 400 /FontBBox [-572 -515 1531 1004] /Flags 4 /CharSet (/section/copyright) /FontFile 87 0 R >> endobj 119 0 obj [614 0 615 ] endobj 118 0 obj << /Type /Encoding /Differences [ 0 /.notdef 167/section 168/.notdef 169/copyright 170/.notdef] >> endobj 84 0 obj << /Length1 1113 /Length2 3578 /Length3 532 /Length 4293 /Filter /FlateDecode >> stream xÚí“y<”×Ç•"#k’Ýe+ûa,Q kHjš cFcC¶Šì{ö²–Â(û~ˆH¤(dARž©î~y~÷ŸÏó×ýºçúgÞç{¾Ÿó¹Î9—˜©¹Œ&wÃa 2Yˆ* edn®‘ ²r 11-< N@ã°ÚpJ€¨¨@M¢ O=‡ªžVP= ‰Z8WíàHĵ$~$AM€c#8ÁåBÕ@À1€9FH²€&˜ý¸á˜¡ÜPxwRH4‚Ü@9 ± ðKúX{ýF]¹£ðnTS€8Õ¤@µˆÄa1$‰²qÔZ(ª“ÿSÿ‡1c¸Ëù_mú·¸ CúW ÎÅ•H@á#…Çþ3õ2ê—;#Mtùç©>ŽA#4± ‘ÿD»ÁО(¤)š€pìá7ÔÏ8 ‹ü§ jï~š›h[[IýžêÏSS8K° ¹¢¹?é?ò‡©=£=«r²rrj"õùýÏöÕt°¥®…¢Çãá$u?¨¤xC4‰òPžTË`Y,Ž@½PûâØãð CUÀ®p< ‹AÙ~ýŠBþý5ÄßaEì†rA#pêüŽ*À7‰(·«úwª€ þõ¢ÉßDÝ0ü)àˆš‰ø› rT{È}Hõ…Ú‡òØ~Rk¢÷!U³©Ê.:0vR•qûªìº0~R ¹íC%L؇Ô×#îCª ÷?(O-Dú‰ÿ¾5çÏã<½e¨í‘‘W¤Q ¥öQÅç%"ˆxê\?¿Jêîýf{4uWQ(O46‚C¨ÝuJ¬ *ôÕÉ(:,áÆ—œ··1°*YMöÓ¤û0¾¶ß%ð™-Í ]Ìš°dðŒ´W|Ë]£y«ÃÛøŠ­ƒ‰C";Åœ~ñÉÆî`gf{3‹KzÈÇ1Á×Z©•qÊ”{ãáRÃy4‡{)ôPáèÈ·u_g¿oÇ“"³ ÛÌzÃû(˹©èžE]\.nìAç ›ʱïcÜÍo'˜’UÂ2h_&«ï2@& Ù½mAæ]þkbÕ0\t“ÑT#òòôÞ¥ ¬¡ÆRŒÎp[eÚ— Üá¦îÓÛE»]7j=ß^!J(yБÌÃD¯UÅû{Ô¸Nì ¡½{ö WBÔ²›/©mØ9ãÓ o#Œ±Ü=.Á®SŒnü="³ 'í…GÓü†³¹À›Í p"y+ÞÈrÍüÄ¥¤-´”Ç4ÊòÚóªÓ$ M ¬ÚrA7`¯e°Úaïnq:Uo-ÍW?ù{YæÉàërEtÍÔKLb-þÂð•$¹²° `º²Òž[R/x™¶S÷féToƒ#@V4aѲc|\{ÖwQ°ÇôV¹Y;w<#Ç\ý¥§§aÉDˆ+ßBêòÊÇP¦I‘T”ö•5ç )» ò‹§4³^õy›á«ãñ\4¨­qö¿¸¨¯‘Êçžs>m…Ö‘ŒPB`­§x7›}=U`•Á'ᢎDŠîô´³qYk{C'¼K‚!à`7”6fÙUbÖ¾‘ÁŒÈYÊU¬wŸy¬?ƒ%dz·Ù^rJ­+öŽy¾Xƒdæ¡cã²|Tž7îvò+éÁßÞ6cä-ú°³îÝ%%7±– ÀÅÆiP¾Í’Ñu)ïŠ×¨³¸’Ýžœ¥ôÅ!!-Ûµê¹Þ\JªenoNŸèGrͤZÔ·éä>bà]HwÒVµiL!t „eMª}””Τ««ä½…ôj¾¿Ð­á{ÿV€NÆPrùز‡§IÛGf`ûR2Ì4Ío‘áë¨ùN¯hq ˜È7Ÿý*î2“®dsܶITò¼ãkÍOóŠìo¶}µÊîN ´›Ž­×UÐh->ù2ÕVO~5Æ"w—ýkIÅ£½­©ç_{mZm¸4Òº9î7yŒÂ²_ŸÞÜÐMð¤8NÆpW‚Gϯ}²¹˜lcÅ+Þ@–1´ºàj½µÌÚ.Š‹Y í´‡ùu‰psu×G‘o}Óƒ×Ü”Ö(½kTóLyí~é=©“Š`Kî~² DÕ6¸Br Áâts&v9 ¥: 9Õ¨¯/”4ª¥«{ŸÒ¦5ôiba½ÚÿQ¦S¼€ø{ñüL«>Ÿ‡FOž"˜¯Y#¥Æ¤²MR‰owO“‹hÞ?zàéƒ4.2©~iŒuÀDô|~nºbJÞJ¸,Å5va>Çuý äåb0¨!Û¹-’3JϨÞ^s½m7Ó±§ºxÒ7l­‹¢{¢^Þz—Ñzì¸Í‹{‡Wp«÷ÌIf2›úØŠËuVvBÚê!—¯²±OIµ3²ý ÓAh ãã+ÌÙºSô*z“V=K∧åé¼®@|k3zžD¶B„ÎI îáN¦Xái‘h¸Ü4ÂÊfŸ1§t'}Ú¶ûlÚâã ï÷$ÚGomˆBH¦Û5)šª§½M&ŒÞ­‡û®ÅrzÂwºëKø+ýô³â™fôVïÌ, ^Zßd6-ƒs[»Ç-;ߣÓyNô,Š ÀÜyeô•ÛÀ&…D«fÉËGÚáu‘ú(VmBùnõûK ³ßÚÏSˆ¢ì@TÄÖ ¿î©Þs¶lçüÞêa9zT¯hTX ?äæ|¡l?lV(ê!¼XI=º^öÞ¼φuœÃú å±]’¸ÙÓ÷Ëã­Ï(; HÑ®‘‚û¡ü™:C1ÞÇó`±2e²ÖøŒw,}våØ,M1„íªl¯…4Aºôx¸x%† eo2‹¯·/ý$¦°vm×ÿ<ü•œ$Ét^ÏÕ´>°=¹üUgGN{Ó‰žµóäGµÌ%­ù '"䢴ñðNNDÓÚ*£µ´É8"šî9ÉOøLC±Å‰>Á‡-aå;òçnIë6DrK ëƒKŒ… R1duÞ&|—Ü‹e¯”Ÿ?’ùJ¯­¬•Pž©ñu霸õ>¤àŽrPŒæ#Í3Ží´sÉkCº—9xÉÌSÛ»Ù¢2ä…ÅÉ(ÙÁ¸³Îѱ”½¶:œÚpw¦TÒœ¦?Ë*ª!ªïT¿Àg”œÁÑ•p¶p4³åÖÄóެc…:áEÍpœ‰¦‡íåöžš@LÐËôÍ“+ ì¼!pñ„ÃÁÕjuòE¡#{kÝÜþ!×ö:30q›Ì[[Ç”™üÞÁãäΉ1[ míõêÜóbÜÇ»–`Z5¾:Î ŠÂÈ·.vwsÌj$üUd;Ë©PMm½`æßu§äé+ë¾›±Mš»a;}½¼ò`nÁÏáqÃU͘³óQ- AïÊ72ƒ"uGȈe°Î½DJ3'ö¼¨çx¡á"¿6ñûÚð1ПFcôd£‘_r ¶Æ=Gúx ×·D0c!ú Ò¼gºÈ#,eAži)Í’ßW¢®ÏR`¾±ú ûS•ƒã[ή1m‘®×Ç3NŠ„”Aéó®µ2)+~+àç—ÿãœ1xXíEøÑø»v%üëtllb‚ƒŒ}G¬·eº¶Û×{ [šîÙS—¦¯v±îY“Ÿ/D+…såâ—QÐýûz—³g ¦l4Úµï&É÷'—PJ^æÃß´“løRKïXòÖîćo–,Œˆ§-™c…Ÿ…FÌŠÆ1³ÌÓ~8iTzgèù¿ Iû‚—î}ˆ%ƒlŠž‹ÉyƒŠ¹UÖ,–‰ bÏ?(¤“Ö<ênLâab"+|ƒ ÐZ®.n~|LŸcZÅbÝ_w2`J¼*Űq¬’_¤ÀÞ+y,Þ¡µÊq3ÏNiÝd™h6Ý4 ]gæÂ:…«iŠMÒd,ˆQƒæO}ù¶XWøéí®6ŽiÕ ëZ¨n n ä²vZ×a—ÂJ‡BÍnÂ¥TÝ=”ÉÓ¿¥ÂÅ;}@ô­€WÙ€éCÎðÄ1ó3Çw7-'ÅgKcY´Ÿ!޳¶%e=æP;×.hšÎrý oYÆåwº`w{wOYAÔÓTÿKZjh~ߤé…f‚!§÷Ñå0åªl¨‡™!f6­ìûúçÑ´õë}V¡ÈëÅʯ¤š¦Å>]ö:âóâ†ÿôNgÀ-šÓÛÎŒ Çü9Cxä¿(ÅWʵ&ešQn9κt± ?Ußî­Þ£w‚uZí&z¤û•65.Ž}-¤_i>¶a´ äJŠ»ÑËõ:rŒ‰-÷Æ'SIÅj®ÇkݺG\ ¿kF‹gs ¯$\ ¬ æ›ã‰¯œQÌË©øP-+Zž½{Ý(Ž ¢‰‹T±¼™ž8 ëd.ˉ饤ATЏ O$~PTRR?6)1¥p娮,¯û‰Àoì_Ò R¶ Ï_[¶šFrW}T»À3 »8T’«õ}§ÂºOòÉ€¢ ¶]˜ ®jì΃·ŸqHLHnÿ þ8Ðý4­ªzV§Þ'ñȸ–á"ãUk¿~)çBWñk Å*•ëâ†òÂ1O†ÞÙÆ?ZêÆ”&Hñ2¯Æ] Ã^3yôe±CÌÕqÒY""!ËXïg+jFNIùšõô@ £±ýÐS<µ]Q]‰….„Cb¬³V{Þ#a]M, ú2øŸ‰¹ßGšÉaùíÑã\gS ³K[µ*¶#†›d½ŒS¾BuËý ÿ üG 0(8ž€sãAÿRô¾endstream endobj 85 0 obj << /Type /Font /Subtype /Type1 /Encoding 120 0 R /FirstChar 40 /LastChar 121 /Widths 121 0 R /BaseFont /OCKNZX+CMSSI10 /FontDescriptor 83 0 R >> endobj 83 0 obj << /Ascent 694 /CapHeight 694 /Descent -194 /FontName /OCKNZX+CMSSI10 /ItalicAngle -12 /StemV 80 /XHeight 444 /FontBBox [-97 -250 1077 759] /Flags 4 /CharSet (/parenleft/parenright/semicolon/question/K/O/a/b/c/d/e/f/i/l/m/n/o/p/r/s/t/u/v/y) /FontFile 84 0 R >> endobj 121 0 obj [389 389 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 278 0 0 0 472 0 0 0 0 0 0 0 0 0 0 0 694 0 0 0 736 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 481 517 444 517 444 306 0 0 239 0 0 239 794 517 500 517 0 342 383 361 517 461 0 0 461 ] endobj 120 0 obj << /Type /Encoding /Differences [ 0 /.notdef 40/parenleft/parenright 42/.notdef 59/semicolon 60/.notdef 63/question 64/.notdef 75/K 76/.notdef 79/O 80/.notdef 97/a/b/c/d/e/f 103/.notdef 105/i 106/.notdef 108/l/m/n/o/p 113/.notdef 114/r/s/t/u/v 119/.notdef 121/y 122/.notdef] >> endobj 76 0 obj << /Length1 755 /Length2 1034 /Length3 532 /Length 1587 /Filter /FlateDecode >> stream xÚíRmXSeVD¡ÉSræ›ÈÇ4ÆlkàÅ#e4˜_¨Xóìlç¬Ãam ÈeȇÐÅ”O›†( ¦( j!” š[ÁÅW)¢&ÐòêŠ~Ö¯®ÞóþxŸç¹ßû¹ßû9ޝJe®"¾úãéÊaq¼€X" äêÈfÓÅ”“(ŽùÉIè8¬‹SwÀæ{q=¨Msb\­#ÐÈ(¸ˆ™S >© "r HädTQˆ\ d8‚BRÇ"¥„N݈¡0¨`Ñ8 @ì„‘(Fs›’ˆEà€?“VÄ©Ÿ•4ˆ¥D—i™L@‰Tà˜R0‚æŒSÝ ¥åß5›Ü?N© –«¦è§Œú[Y®B•º?¸JGBHp$°ÙÐÍpF›*Ð8Õìj )W¢ˆ‹TBàÊñd±=gòh¬?ª… )J"Q B®Œ…Óyˆ)f+¡ì›Öá¶V*“ÉBWÏ vº&•£¹A§†€ý'x:æüS¨lc³Øl¤¾g§ðY½ÞÄ\b‘ÀËr‚ëhlŠÊË €b ¨PK vca8I]”1»ANЦ¦êÉnT/ùÔcgF•iޝ/®M`Ww.ÅÎásŸËÞýG#§ÿÊ’gqJ¹¡"´VŽx'GçT¥”&¾YÔR6ŸËÈ;™|½n_Ež7k ÓÛöq±y|åú>z4zÊæðÃõgÜÜnû*Ñ®k 2·}ßѯYÛ1åË,‘í;Ì WªŽ¤÷¶.¿§¾¸z°Ía¸KS7ÿlP?‡ÿ±ýÃýæ¥ßšÆ%Æì¹ÑÝj•«pè9L 7|tnêëÊúúɃ¼o-n\¼=øÅ‰Vû+·cÚ­ó ’yßj]¶îÇM^…gÑò®o„õ‡Y§1ŸÏ«Ï8F¯ÁecÅχÔà¥Vò*ó¹W¸gO%œÓÞ¶Z¿_µgÞœf4Ý4YÈl|ëúöÒ²Ê6•×¼ÓÕ–œvÚûcåMÏûiýæÆ&mˆ$1 f4g™(± _K—¤aí%'Î7/˜wßP|Gà=p{¹œáJNž˜¸ûä·†Ÿ·%1îšr›¤a3Y%Ì~4RþH—3/“wéî)Í•þÄå®Î%ý}|ÀK*kë>ÁѲ𦡺[Ì/mùqFSjjñÁì9cÛÆòCôK!QžÌZ×Ô½WsÔs–Ü/ʛӔpäõ°»-ç¤Õ«¡¡fûŠš3Ò E -Ùña}a=K¶Ž Gš«?x›!}œ)î:-þ¢·]ÿ¢ßÍ ±sþøš—vé]Ò‹B™LRº^´4Zh˶4?•,µ ‹_Pª˜l÷YßyŒ¼cP‹4™»Úè«Ð$é\]ŸnÐäu¬0³þ¤õ«÷éW…5¢÷†o¦¾ 64XÞ>àEX>f¦]æÙ½Ö[\˜ ìIëß»®7¿%êI¿ðáºØã.sÙ)+íF³UWOþäÌîìŒ^”s®Ð1s¾ÓÒÏ‘ Cò{7Bõ'Ö>U„5Y×ûÖ›ªÞêâ™­|´³zëÓÖ ÕøŸ±“ù¾™YG+Ú_+KŽäß.^ÜÍ[¸q]- °pÈìHñyCÊàà.ã]6.5!¹â–ºã+ÒG·}ç—¾¬‚uíK[æ7>½}Þ¶Yoé 2¦å¤Þ¸¬¼×ת" S½ã"XÊSD2Òýý^!et ÄÉqÿÈc½xAõ=w«‘£¶_«†V_Û¶ßíÙ {ÆýÆ”Éàå̶Õ#«íãË6ŸœT|î%Öû'­©7v[¬Étþ¢:±$ƒ¿ûúB§[.NFÛŸÃ…?ŸO/ÛZ®ï½9Ú9á$pÒÆÖ†MnúÑ£òaÓñ…F³¦•9u©G-4¦cO£¿¯ ïånó›d$›ôVÍÂKÕí;ÄO–,{0V ÍuD*œKÄ76†äS.Å´ –å9ÝÙÓZ`à éˆ+àÕ »?œs¹ïº0áÝ·ìÍžs±4†¾ïõƒQ™Ö2Ò“¥ÑEÑK -Ùlcù3û.Úÿÿ D 剫äD íwø bµendstream endobj 77 0 obj << /Type /Font /Subtype /Type1 /Encoding 122 0 R /FirstChar 46 /LastChar 46 /Widths 123 0 R /BaseFont /HPSSSR+CMMI7 /FontDescriptor 75 0 R >> endobj 75 0 obj << /Ascent 694 /CapHeight 683 /Descent -194 /FontName /HPSSSR+CMMI7 /ItalicAngle -14.04 /StemV 81 /XHeight 431 /FontBBox [0 -250 1171 750] /Flags 4 /CharSet (/triangleright) /FontFile 76 0 R >> endobj 123 0 obj [585 ] endobj 122 0 obj << /Type /Encoding /Differences [ 0 /.notdef 46/triangleright 47/.notdef] >> endobj 73 0 obj << /Length1 770 /Length2 723 /Length3 532 /Length 1275 /Filter /FlateDecode >> stream xÚíRkPWE¥$ØV¨>ÊUÄ! D$ÇAÁ`¨  ²ìÞÀJ²– &ÍV)ƒ<¤è Ž ’`-¶R‹qF èhG´Êg„BèPiUº:Nñgû«ÓÝ?ûï|çž=ßõ\¬T ä8•Ã(’ BDQªMŠ9žž ¢ A‘«Qʈ¹> R I™TÂñ Jg¤‰Ô4x)¼m¤ ×BšÀPD¡LÔ²ª* # c¹F6Ø&2Á˜ é,ˆ 9pc@ L%HŽÈf(‚TS `Æõº×­,Hg²¦€kÒ°qŠÔÕQ4ÅžY'ÿ†©©âaz&ÕÚäm!½ÕFµ„ÆøÒêô ¤A…CšœJƒ“Þ¢ NèµS» ª!09™ª@€ø Å~ÒÉ‘F ®$, ¨QM&œÀ!‰OµÂ¦7aD¾&$2.†?¹Õ‰ž%H&Ö¨ƒ@ü†Ÿ%ó¬µö}Óæ¹û~½ña <^t¤¿þr1 ór‹(‰ZqîX½G^çS~²Ghq½~*zÈ©­í=ÅOA/g?æ»&¯¹ÓûÝG½c¢²¾ûJ]Úm.wFÌÀƒ³¿(†æ4säæç]•Aß§K}ý_Sê-'¶8^XrøÁ‹ø;q0d]i%oeŽeU³IX6¿qÏ^JbS㌕vJå}¡S}×àÓ:÷Õ_ºÅÕùþ\jzéà±qÚÜø¼‡Ë»ÛûÈO,*/LïÌy[•|(ý½mÎPtɲàä/²ðÃÖpË NbyÞ7ƒcÃLYW‹v–_Íu¿×½gÄ„¬ãëÊV¸ªVzõ× &48盫Î×Ï:Œ½¿dÕ­³Ü,Sµ÷7¯W¹×†^åèr®ù*âön[€Œ?ï¿ô´wöEiþÚ3½úé¢Ö$¼Ùá¨uQ©%ϵð´·jìö=»výâÖPªÚ+<bðÛ…§›;0±ÙéçàÕMåüØPsŒß֮ȼ†C²O'·ý3böMÜÅ‹Ï}Gõ|ýA‰5©Ã>½¢;|žèƒrѶËÜÖŽ› =þt©rtÓtn«ý)PkpkĨ;…9Ië¹»´Ü8ÙÚqÐ2Í.Éßñx¯l4¹øÅ¹¥û«3͵>%ôcIïIÇsÑæ£Z,P_Q£±¾HIcäsljYêb·›‡|\úFjVi­3éq­ôSÁÝ'‹ƒÆ—-¸³«QSáèpúM„GÜ´Ú.o¹”‘»öIÝ­í»OÁèünãR•â~Ü%ÆŽ7·¨BüÎÿÿ LQš¡´(Îùƒ%À endstream endobj 74 0 obj << /Type /Font /Subtype /Type1 /Encoding 124 0 R /FirstChar 15 /LastChar 33 /Widths 125 0 R /BaseFont /GHBJWQ+CMSY7 /FontDescriptor 72 0 R >> endobj 72 0 obj << /Ascent 750 /CapHeight 683 /Descent -194 /FontName /GHBJWQ+CMSY7 /ItalicAngle -14.035 /StemV 93 /XHeight 431 /FontBBox [-15 -951 1252 782] /Flags 4 /CharSet (/bullet/arrowright) /FontFile 73 0 R >> endobj 125 0 obj [585 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1139 ] endobj 124 0 obj << /Type /Encoding /Differences [ 0 /.notdef 15/bullet 16/.notdef 33/arrowright 34/.notdef] >> endobj 19 0 obj << /Length1 1743 /Length2 10238 /Length3 532 /Length 11247 /Filter /FlateDecode >> stream xÚí•U\œËö¦ÑàÁ-XãnÁ-ÁÝ%¸Ó@c McÁ=‚wwBÜà<¸C°`ÓûœóßÉ>s9s5¿éî‹~ÞZߪ·ÖªªžZ]‹] l”;AÙ¹9¸…R*ÚÚÜ\n..Itzz)Ð ;I›CÂn!¡— gà%?€›K˜÷%ì‡N;{A@6¶P“ó_A G diîP1‡Úa9,ÍZ`KêÅpphþõ„+@è „¸­8й¹V K(ÀhrBçüË“‚“5 ðoÙÊÍù†ÜW˜)Ó¿l2`&­ÀN^+ 5:§*6æåÿ†­ÿN.ëæà jîøWúUê7w9xý'ìèìB*`+ Äé¿Cuÿ6§´¹9þ÷¨ÔÜd)ádãpý[¹Ê‚§­—³-Ð鷛ĶÀV¿%§«ƒ¹«íoEÀùÿ„œ`'àßÌóõø=Îsµ…ÿˆx à´»ý^ L¹ÿ³ë +÷ß 3ë tÿÃ+¬´œÀ,‘fÕ ô§Á¿Öìþã!¡¿Ò8‚þ©òà ;]] 06sào‹ü°ù$l.©ßK+ó7 ÀÖ+÷›`¹äl¡ “ lbõßÓú›„`Y, æ–ö@è?ú »n8-`ò?›"Äówô?û;᜿{.«ˆÅo‚Ù¶ü›¸¹`n¬þ@˜àøWÏþ@Ø„6 ¬_¶ ¬] ?V;»?æÉþ„™røa®#ìr:ý0Wà?æÊù„Ù€üíš?fúÂl¸ý0î ̆Ço| ³áùÂlxýÿ)?ð­â~É÷ùŸ=ᆽ+8Í]-A (ÈÁêß5þ߯+II°§7;/€ý%lßpÃv —ï?Â,Ý °sý׫våý[ƒ`÷#è ´D_˜[Š„Ø%ׇ•úÉä—!³ÀKÚ4ĪÖtLµbÏÇÁ; )¹°¬Tëý*Oþ¼MåqOæÑâ£1*{è›2ó¸ín¶öö+™ÞyšŠ×–3ÍuðvsýôÏC>xµÉ•áÒý‚ÞÌÓ¾ƒ\u&ií”ïÔp†îµé!üz²i:á¡Õ <Ôš8).¥ïx×=p>"-ûÙ…£6².[Üð~壿Ä?šÂÝZ>‹ z)™5ß0f'žÆN*¸l$ms»eüWSÔÎIo‘^Ù™Z9Ýä´Ð|½Ø>¾®Õô¥Ó nú~Í‚e”t§_™ e\ÈU÷¥\Ké@X¦¡BÇÞñS°|(×Ëb™ŸÄ ¯ÓÕŽ€¬ÁÉŒ%µ¸oŸ(”¾SÖáÔ‡'·‚¬âk]lË2ñçÒª‡Ï |{h?Þ÷Hê>â}æùÙ¾‡ú„;–L 2{èœ×Óùã‚)÷€þý%Q‡Ó_敤½¯Ä¾fȲ%£Vb!bÙêú»`ö õ.(Õ+ËŸ®4AŽ#÷y¥š ZnÙ¤"ׂa!ýNgr­âCg.@ib7·Y Nõl!pæ]CÅ”ŠÛòêV»½§õÆÍ¦4l+}±Í+jh£d·´GºÁ8Æêšq´>u¬¦”a¢Þ?}µ·_"ÃèÔ±ÀâÿÕ³Óy‰SÑ÷®<û9Ìù€_ ~%fèî3Ð×¶;‡/N—ÿ[yYdaG=S0ÈX¡õ=UùÉ.( }ó(´6b˜†&‹Ð‚'uPlgÏUqqñ-E'î´âÌ«|CÝ­ñº{ yÀ—4KÑ÷3SÏ o›¸ÔJ¼„8ÖZ:Ö‹(¡5¹÷ÂbÄ$st2ýeX}ƤPªÂGpT»=ú£ŒR;jòRlFp¡5$1\óHÉÄk=ÌÌ]Ð$ì4ëW¶{èù%‚߀‡fxÍshEUEogœGþ…Vž3k±6›Ë3jWÄX®1¦ù¯}":I¥µ¬<; 4 Š‚.<ÁSVŠgfùUé¬;û eMn•ǽËëóí¶ò¢bÔŒxå_)´G“^r”CÖéÑÝ1!VÈ'?–††þònlÏP_}l]³‚ïaaÓŒŠ0ÄcüÖv:(ÕÁ»¸Ð™]í[“ÚZ¹ÐÒ§ êù¸G‘ycøRÒ°³G\ɉötÌv¯-)<‹:ˆJŸè•·S_ÌÅè÷µ# ÕÙ<§˜ÔÞðë‘„D`TN½Ýûö‹_CsïËDzÞrTJ=ùz¬C ´8ù©-`+Iu¼üÄÁµ~qê9›ºù&Ô‹½p"vœéý õrùvy•Òã{ˆÀaÍa| òªÜ;•¥‰ïömâ)«HyiW¿„7¥Í°Ð˵j‚°æëáòáÈ5>³ç§­Ë`ýÍ÷€¹/Á$øõ Ÿ'æ#•§ê)ýbö[Ê›ñ`œcå7ãòNÖ§‹íÛΫó?{¨ ¢yH[ýWJ™Ï´U_TI§Éüj‰3Eæ¿|õnu¡t½›xFÏO!ïG»J 3ôn>ððà¡XO7Ó1uÎÊa¢C-·l³§°wŒ­ÇÒ…2-àÈéV¿4¢$ü¡œ+ÁiÔ_0ÚÙN¬A¸Æ'“]¥ø:WíÄx ‡&€¤Ç*>Gc7mÏ“Lþ¾õ½…{H—lÚœ”ƒ°_ö…¿%JS.Pxåþ#sÅK'^zg’k“xN²Óâvñ߈»H~Ž 0dø~x/QŒv`⌟ǺýP»¹viì`r)Ä€°:|$÷±£'c«ÖØÈÕ¨L #ÛôV!+ª³æ.ð»¯(ä° ®FÕGÊùLø üá9t–Ÿ·I5б|ŠHHtÈÀ¾”WF×M*ÎkÞZIÏï+Ú²W3왋ø¿eEŠâ(CX]¾?‹U{Ì“°ó\#“‹Ó@m›/í׬ñ¼µš¦s¤ßšv¼]õT÷ñ58©U±žóW&M=põ/D[6”k3dbFZ!8QË(ôŸ4@±†¦ÄŒlTpâ‰<+ûUËý¶"ÂÇq “£Pž‰lºûÚ"‹¤ñ ;Ÿû£+•ûÇq˜0:™{?*‰€¿´Û=2Žìøî·íi½ÝRõ–;ŒIvËŠ°4 èÔp\w>FÕ°þ(íP’J4”Ï2˜ZW®‰­Å8\tà 8â.©!©°†ÝóºKk"TSð»RÉnä³A/¥ÔüëŸr E÷3Ê?Ðc¤&£¥jëNÑû™³´ÛbNäŽ×A¾ÙA.)ä bÓ¬”tni{ƒuÀçõ6[ñ±OŒ7:~„ÊäŒËë" 'W¿R*V²G4Ö«oÞ]¥‡ó1{:D|“ô½>>>rÄ(5B‡7¶ZI î&ψ’úMÃUÔ¾»àÎ!ùeëÙGùwq†¡¼ÔÕgHi¹L ©›¤º_ªXS‰JÀ’Þ;¬ ×ÍûSÖsӴ׋A…µ÷<)”ÑWƒ˜tq©(G¯ ÞžyšXât;ElçºÈZ¼¯N>ÛW%ó¦3˺6œê´-K7NÚSš“°“1K•œn5{H‚]ž&êDæÂr¯jn¹á†f/ùl#s4”K,¾Ñî^ÇHìüÌMÞºe¿»ôÍö¨›PqøÕ@y=ýºcÇ÷ÊŒ;_è'mkí”dÁaI®µADç56þrÅsbŠ5ÐÑÑqNA¬}êXI€Ì‘ÞÍÜ]®ãî­¼>‹Üf³ö0¨û»h¹ü.aT›Aæòã~Àe+§býíðTÁLËfÈ'NR,$­Îñƒ’õ¶Ó,5÷vñHž[cÂê&…mÜû/éöŒ´3?cø™Åˆƒz‚A¨í-_¼Ú«Þ)S á\?eÿT.ˆiGmèY[ »æä–ã1|\_iLQM¤ÇÉ£äf1¾h3üicLýi‹!ulwð‡[Z"šbnWïìˆoÎÞÉ':ÒÚø?^WMû z?‰œÆ±ù_oF ¡¼n ™}q;’¸„6´cJ“##màKV½\܉Uð>†fÒ¥á ÁìE…¡Ÿ| ö@öÖí…< Ÿ&uÝ„L5aj/ÓxwãÛgðß?žÀ)¨CDÃ3ê"÷èjvÐFŒŒ”{;šÛ÷Ö^ÍÆ¯Üö³½ŽkÄÔlÏiW ´¾öè¿Ûµz‡[°± É iËg·Mƒâs¢6Ü|… ä†¡Æpr–Ÿc¯ø»Íy›^g]l«à¼ï OŠé.³†øa(rÓgß`Èx&ój\gõ´¾¾‡„Ó¤¤¦pà´šûùLÒ5ÁAèËqðïo±ô=>û7fèÙxqÐUÌò(´• V/É ö?ÆÐtg¨åè¢ßœ²07Z8ê4ÎYxåÕaöûÏi×àú¿¸ˆEàhX„ç Ë`¸LJ:î&Ú@TíÜ%û¸áÒæbÃtû+ìœ|“SôÞ³ÚwÕMÎÏP>eKûc½€-‚òüúÅ Æ Ž½Ã•ËŒYÁªAâ³/j"Z6½ý6$ŠrÝDzWì$€|óMïüŠØþ‡Gšµö‰žêÃìÔ´ØùP2]޽¥äÞ¯ñ\y¼ù ÎvÿON™}È]ÔÙYHÁï,ÎòZæ®Ü#ÙÙZ#¨8&æ4ýRÔ¾!<“QÈÝI·KÚ¤’%%~«¼Ïá‘Éà‘±°4Iƈ2WJ%W-÷ x •®ø: ˆ™ŠþV/}?ÁRÄ›ùÛ>Y½f Çè‘Þû3iÓÜßfÝÄ#f·BÀ(W0q>ÒèŘo&…IõeõZÕ¯uü¢æãÌ?áÉd¸Ž*}Ý~vG‡ý”Zx7ä/jë“4ÇÓôV {­Ù«Ï.<ñ§FÓ¾|Y¾î˜¸W K`¦ÈîI¯rua¤Ñ’²«©Œµ®»6r3&Í~Ó†õ>å,0É[Jw·Cû^áôùÀÇ7ø5s,Ù.o@Bλ]YÍr¬„²:ÌŸG‚È i3ïfùl¬Ey"oî/ÖùÕy23ööoXg6H ©ù†ùÃ÷2ºµd•ž6´àòÙX— ©IßÕð¥[‡s8øá©òaIêèªKÜòo‡Q”,.м·Ž«2`…œ´V*IXø÷új:]¬˜©ø "T΋1ÌÁKm'à®$®žˆ‡ k9vžñs¢LO »ÉcU“õ6¿'H¹QT0=oîÃ^ýŒâáë!=|Z®/ËÁÎüC¿)¥ZõßCºëùÉ„( wSA0Rˆ!Ø®_ 2˽Aú™ ÛNœ¢(¡©Ýïf'•k_ÞÞ“çV|;oùñl ^©:4ÍÑÆ5}ÚÜé¹PÛ ]‹7J©Y=¨HÓ¹råâff%’œì˜IWfŸ_˜ ám8¨œ´Á1ÿº­áûìZöm0Àj47õ„âq­Tp{ʪ —Gƒj?v†(îhzŽ/h;«QX`]`ª’ûŒTŒKC|÷#yWZði9xxÔúò™˜5U~ó¾ssÁæ›*KÑ”ø¨ }”û  ­{ÎôŒ†ìŽ/zvž‰Ù×𠸵{Ç %qÙÌD.Ÿ$&^5Þ‡ÈÐ¥…`÷ÓŸß\8¨­!ʦü¨"„ˆÝzt\Sò%8Ÿ»d™ËÕó÷à2 ’ û°l¬Äm.e+r»oÆ^Z—râ鮇þÓÏ<¥Ø(¡»Ÿ}ÑÐ#ó•õ ¹¥óöqGݾ˨µŒßùÙ{JalõU9«Š!åÞ.“ ^âÂÛÏòÔìyk {˜Q•µq2ðò–?a43Vþi¼ˆAʶöšøˆ­6ª»ûÑó£Ÿ:*uju;a:7Z)ßúшÔE­|%Ì%ƒ†Woß> Ùõ5¤ZíöE;aI%œŸ»–}cœb­­2ñP¦6@BÎödÞŒàèwé<¬ê#¢˜^³ÂŠ[†°Dr‡:ÜÌæŽ|½óe„m¼±ý~þ' @œf¥°%r±'Èt£1°sŒÒgüD>ï¡\,EÎ}˜Ãi\5ˆ/í7õI,ב.'=º>40Póg‘/¾³;iÎшO†;…ŽªSI³‘w˜é'&œ hÁ…Ê èYEd5§ œß`]¶{QÎÄ++³//WvÚø çŸí»íw""‹2Ðq_3¹ÿzðM«­¨«L]y¨QŒÓ¨¿1º¤/¼É­ç?•¾ í9÷ÎkLv TÀ¯جn މ(´ÁôÈgQd|;?qþ9û¼h0%ùY@a½0‹ÈŠI­^NÚ%* s–å^ k2fÛUøñV0›Ô¯†¦ï.%\:(Š!´¾8ÞE;c“ñk͈žúê"3‚Ÿ‡.Ðs cpUR0,¯#<ò%|oø3lˆIÄ?˜‹jx(„E ‰Ó4¶ Ì”^³¬‰‘¨Žì„­ |Ë5Ýjœ0B;î@>~µ‰ê`téiãÙ#uƒ Ì”ÖÏM8„PÆbqä„“ŸÒƒÞ™@›ôôBmW´ˆÈ†8é¶#[œˆúò£“€ì—°ú• Á#?|u~¶« »Å¹µ¥ç rƒÑ?>ʵ€(½@OEÎëéyE5Çð“Çcibdñ«˜Wf‘XºæÛY¥¦äHÌ’‘Õ”2¥áIw…¿ÙܯëÍk…UŒ¶‚º£}µ ƒ»ùÓkeHÂð ‚Pòy2ﮢm>ùæpâWuz¹ºÚ’åVŽŸm•,üa•_¶ÔÞ"šiÃôq£•»DqëNY0RѽC‘R·˜WZ´ÀëbCÞgDä­+Յʉ–Iõµo¾+ï*)Lß{…m;ê‰X)Qæ-üÚò+) ÌŽtYj¶OYE~BÐeSØ2hñá߯àI'x÷–-:%£‰{M‡ÓÖ' Ö¦··Ç¹H:d+ ˜8%íY³\v`xÜv/N¢:Œ¸¡KÇ«mëA?Qe)R~ºv3`FÖ¥öL[3Wb"©´.¬þÚ»¹ynœ7LDõ5ø}žŠŽB¥¼Àk?FÚH|õjùÆT_£¡ýrôˆ÷…KÍø„}N6s%$¨ 1J‘)çµðKaÊÆ‰ ?åŠäž7õNQR 8µ]sê”íuŒ×âÀïIŸ?W¬jÀEàåðÎ;pŸýMÏÓ#wå¤*-™ÆãwÒé÷ï¿KÕÍéh›et×6}“ob$ÅÔ~&îŸØ¼ÇVÁ;á ¡ÿ`¹*Z06´œ4ÑÏçW\_ñ|ŽŽßò¼²—Ãͤ­ù9|ܶ³°'Üð"B’±-÷)tµê;IüÄïBg¯èã4_"´:nƒ Þ™'ÖœÑ[FC®Í8P;>ÊG¼?îçí{ü×7.d´Øû- eOöe%@ôùêǹ\Rýp…È\“&£Ḇ![®L »^Fã—Kðr|À·&9Mfu¡Dy0ËKM;7àÚ“”„òƒQõ-qÖ»x'.fé!B;%ÓÅÕbSÒg9úÃ-%ý£ äú{«o¾k¹R¨¡ùPòÅBu`2{Ëѹo?Õ¨ë{›˜œyÿWx"ÓxzA0rŽâYÏd̑ۇqëmäbu-*r8x*8Ýâ«ç ‰×Ö W¢€ƒêcìj`vAæLø•¨pzJ8GµÔøiFB%…'Í(}%V­gUn•Mßý6FVÕRx|#2ó¹èß/¶P;Ö&_Çg¸²ÃïsÇl³xÖ¹â±i"Ê+xr›ç¦ñÂz‡;¹fkŒê©Ãµé½=×ë`¯·û©´*ïÜKkJD L5¸S áW(§ÞbÕæV=úQ²,³ä‘.m]*ÎÁ/ž‡1ùq·˜pÑS¼@ì/Š-ëŸZôeG¸Lõs°|Í¡*¶hT-x¿~‹ »ïãHê>@f„½çKü>•,(ÑK¿>MLwþÅ壜¦”Òl ­'Ãî´Ryå˽yMÞé!¼ÈkjaÏb¶ç5Ú¾–¦  ã?jÙÊIÈU€*üO8¡d΃M8©Ñ&W£Ç9‚gWµŠ×Â÷$Ñ1—f¬ÕˆoT4õYã7û €èv•GE1ŸÆ*?ÝNGÚ? ­ºÉ+Ø{e‡X²¿ 5œ /{|b¯û¦ÔÙîô˜WóxÅý þzÕUž2—šYsÅÙ‰0V§™¿ “ŠêF¼9û¡ù°ª]¶C\=ÄJ~ŠØ¼£Pc„"2›(dë3ÙXêÐ÷:xª†Qò+«ê;‡zÅ‚JÜäeÏùÑLS ¢³k8)~׫gɆFðÒ|äŸüŠô¶ƒVzµŠâKP¤”EDðSnÏX‹“ÙW•¤ÑUƿϟ¼ñ¢:Œßö@k´ÀÙYÍx—½¾·3î@?%öE3¯ÀUl.>piÁ@,ÐéÝY·o3Áï²Ý4±Øšä“7ªl lá2ÂîFg´Ý‘$°Ë•‡J–‰¸4H1þT%mÃhÊØJÌôœ ÛLòÃ)Œ–ØŽl*ÞÿZëøÚŸ¹·õZÓ;ó”[±¤»ñè&_Ç ‰­Í¯¬šHçE`[ï…AzØ æ8A.úšÏÈ›¼æ-¼—€¨kK½—÷IŸLˆ‘e.ß—¦öOd#)©¢ø×â _S÷ÔÊxY¨tcâ.¥ƒFˆÈxlƒ†ŒoC|݌Či‘¯S|EÐV½kï·2PçÑGØý Hš¿à¸6K/]ùc3²—j4Þk<íHâõWË ÄI.Øe¨J‚$dUb sëZYaôÇ P ¾PLšÆq.ð'©ã†Ÿ¶ÔçH[{0øJ(µEáŽü-éøŠÜ‰{Núà )¤îF¥Ã2q„x)1^9K›ëá ë0Ó—)*b4öá;´Ka43ÜÒQi:µ^%0»&ÚhÙV“¢«ƒ^Š%%hìëSã]…uiŒRܸ=U|«âx«°ó4‹V=˜)ˆ‚€QÐÏ}1Í™¿|–ì¤|R=K0“sÐÇ8Óù·7͉7½X:3§ó-TÍŠ¦ô½Ï<Þ®7Ñ Â¤½#s´ä”– rÞ9„J5uYÉxE¼9óIöÔ p>¶8"fJ–TdvꡬÝOô£Ú5%#–jbײªñ7±|Xªw±PÐòÛƱ8ús!+F Oeš|'g©`8Ðld$‚Ùõ<ò Ѹ؉õùÚ‡M§‰Ÿ}o6'ïͼ|Σ®—æ&7sØA¬ý#û$jèÏs{/Óö³d¥¾—‡CðgÏv: däJ:MÐ]ZÖŒehú)ƒü"<å½Tå_"rçš&ò€Y _Ø›g¤S(·ÜÅ|ŸQTP3Q<_t«Ø#6ÌfY­iáÙRçÜHúº3°ð$fg9ýe)Ö"ù(ö%¹ˆh('ÙùG•1Ñ`vÕg ô(ZàIžWš1C‘%ñö棩ºa|D§ÊP©¥½";0f¹äts>aÁ:`b„UŸ fJ ÄUýÎÛ÷úÙ|­‡Ÿ,'½ ?jÊÓGœ}›†V&x}/žäY±dUSýnÿ>ûD³Úm¹ŒX—êr;þ½FÔC"4Ö!t©†ó—Ì;}ŒHËTç£ø3J­í¶#÷aÛ"¿®^£E›ˆÅlû7úU~Ò©ÅÔMDZjëï¾'Õo•Š c)òmŽ%¬v ¶&Ýä®K2x ef†‘˜ñ°ì£Ž>ÖN½!? Úh îžÐÁ™…ë½9^,ÙŠ±þHÖ˜lbôŒYêCçÞ.Ηn79Š®øãÐF1»E+}\’.Õs 8)¨ÚîiÐØÁ;e\”ø`¦É$ctOÈå+íÞp²ì{AVó=­ŠÜlo—7“[7ï½Å6¸6(òuD/?°à-§qiX’·æ´¯v—fŸÆD2‰x*•?œ ×¥Y!s¾ªÚ5è$TÔxkëámGWøpfƒ3$ûP²÷Ñ©‰«ùò¶Ò<ŠôSïió[ûesP ¼Š]ŽP¤DCyPa;îî­y».V6"›Cd¼ä•6éçS>ë‰ÉÌb¸Z ÉÎÙ8Ÿ6G=ã2ïõÞ³Öf¯çñœ®ž+\¸ø*.›e/pÇ:aÍÙ~ln6ª¢|¡¤mž‡IäF6§'òäÛ· y’jös ›üÞéDµ,cN¯7màS)Ê>¼¬8¿=§—ÒþÕOU6É$qÆ †“>öPݗˤ É4Æ ìæ¤/D‘óÑÐԻЃ^ VÜs©LqËf†ŠŸª~tí=ÉùÅ<¶—7úùpo›­/ì›…oD¶ÎÖCØ';²φ.Ë·>ã¦ÜIVvøý6Óµg¯Aúxó™ú‹™‹¶§ÄŠ=”®¦Í ¹Ô§³Œo,¯¦DØäˆi±ß„ä³á¦Ù·Íèpúsm£^æ•l]-…/‘H„c'ÈhªÇÊÆã–ýÊJŠ}Ùñ¥¶Œýáš·Zx€GH=†)8<ø~úMw=b®yuBW΀LoWWÜ;Ç9bóü`޲“÷Tù¸ýŒ|5Â7p Nÿ-¶WâÀ²ãUѹCØøjê"ð<`û4ç…Y9íÇpÊ«Þ1-Å­Fôq´uR¢%€·2/Ë—«¯Ÿ Ñ/iß”ßÒb>0ëÌçtõè‹®J¡Ì©ÊßÁ5!uÔž7}åIÍfÚ ’5Ù }¢8((ºðèdD¼ŒÂ xë§ôŽª”鄺 hâÈ™ XðåÔÒ5Ì|¦>yÚȃ(´DtÀb¹"/u9ùëË×r_oÓmسFjWÙÑ2fZOCe’gTì~©®YùòDYkÒ`º¨ò‘ÌœN¿ÛÎ`Ë©©:¿“óš[_zé¶…à÷-ßû³|>ÿXŸ=TP9Pæ©›¥ªËdñØ6ÀÚz¯D#¸’ÊÏd/AbuÆU'òæ“)cn¬j®s»‰ÔÐÍ9¦òW!6¦J‚²wnZ¯ÐdÐúð0„–Ì8µ$FŽë!ÃC6 ækp>7¥‰ hÁt£ö&‰i›Wg L“ɈO:e|[y4¬÷á~%tßü%´ðì2œÌT ÒQÚâ6Š«¾ØÝJ ß•þ|eóI±„áËÎ÷6¸%U¤6"‘ÑÂÿÔ>ã¤'ç$‰Þyt­ò@'û‹‹æHSµ]ºÔÑ®Û5S߀Cž|À)¤ÁÔ%†ª SÓ–uû¸ØKä:Ÿ‚ ù½N&#¡ð¹x·‰|F®‚òcxšt¶§‡ùãUMá£Ü•já-ß"Ϥ¨<{Où$åIÕgz“±º‘òx@+¾øÕ-—Ùfâ}†ãê$|žjNCÑy"Çuo¢'ºÎvj½~(Þ @T}‡jX&ܯóÑtfåå4=ñv$ž8¹å.;k¼’iIGÉPP؇l sg¥Á¹f1·Ú.ÝÕf®â:§ûùÀ†¥ªâ¢…›»O+x&Á×ÅàÉc#°õç¤Y!'¹Èà7›èi8»¸ûqOU¼Ó~â}I[4•ÍæÕÝmd¶Ãé×"®{HÔGü¯tŸ$ªÓ¡x.fR]B· ò„áf˜a±–¥²¡Ù±zÄÚÏ›~–ŸqÅ  ¦ÅE E}³3“}Ç[f„eø:š6›¡`>$õcyËd%6û€¸¶÷Ð0º\…¼¡Â$Ld„¸,=¸ôpUU¼»-ÌßëGsmÕà˜Š¡Ô N!èG·Y¢DöZPE±×°*õ¹¯_t:ñCë8¿’­!YmÔ2ÆåÓªtêÂ멟ã>â$­Ì T|Ü_<ˆrèõBÄ0fOBwëíó9ÈåZ[&Že<‘~|Ƕnk8Ý' KðRDÝ{©úÅ/~’-@t¶&Ha]9R.F)d5‡G/Ú£]8N³B€g¼p*s:Nÿ‹¬€;Áê±N‹£ºíIoÅ©ÂËŠZ©ÖÌW/©3“ÁcŽ+"-5Õá‡SÅþÐQîf¢àJ„ÉBA˜«@šo‡Xhw]OtJ¯oïÃÞ'­Ô”N‹vÈDoÄ£ã¨$” ä Œ§ŒóΞ3^Z«`œ:Pð`iô«h—’õ=¥Ÿdïœ;#äµÙ šsžù/sk’9õR ™X7c1©©;”± 7±7i(yT?x£ ¢)a8ß=vH›LÉ'µƒ¢‹¶eø¥ê¼ÝRQ(Œc×øõQã¡ã⑯ˆ§µ4Q„Ü‘[Ne4MM<üÜ—äËŸµÌ "Ú}j¬Mx1jI¸(Šp.-¸á·åzå£ZI`Dzå50ÕV~}?à í­àT_(Ýõ›ÑšdM”_+U!º7¼w÷ýÞü‚û-Þ”B{ü•‚OÇ$è”A[¶ÁSÁn¨§¡B¥r üñ&õ­ÎHˆ?× ¹¯˜©âa%”M$K-ìgaÜÍÉŽmèð÷1&cuÏâ7m¦‹)®;)ù—ïšWE›\/{ÄÕ‰“ëTßÂwvÙ ¶ZfN=¥F¾§•¨¢uI(/åܼ--Tai´‡j„œ1œik£U aÖ/Ë:üƸ†hNÐRû«Æ Ò~~˜¿£T»siôõ¸×7Ðq©%# ý‘MH±ÐÔ[ÇKÞR;€XÈjÊPئ¼2qÒ*Z;É£¿nù$ˆ}Ü‡Ñ Æ?±}ò^£ 9wƒl~¸é•l¡·¦ç)fmfö÷Ž)RÞZ¢Œªµá‹Búp|/ƒaWMš ’¨(¡}¡=wûR¿BÅø¸ƒÈ®å"¸l¹pÎ^ž¤9\¿\B³u2V䄜­Qв—³ÜÓç±ûGãëñK0‰„! R5ºû^çâ² îâ!ƒròUå“ÍíÒlöU~u IãK óZš‚3ê²VÕª³7gÊNBêІÁ-ÑÏõpº0›»ýùçw«!‰*_·¶,’Yw£­jg+ÆwLòÕ¢½‘¼ Ýìh[ä»åv'G#^ˆûH1Úø”{rŒ1·ºÑKeËrý~Ðÿ‚ÿ'X:Í!P°£9ÄýF»šÍendstream endobj 20 0 obj << /Type /Font /Subtype /Type1 /Encoding 126 0 R /FirstChar 34 /LastChar 126 /Widths 127 0 R /BaseFont /UNLNYI+CMTT10 /FontDescriptor 18 0 R >> endobj 18 0 obj << /Ascent 611 /CapHeight 611 /Descent -222 /FontName /UNLNYI+CMTT10 /ItalicAngle 0 /StemV 69 /XHeight 431 /FontBBox [-4 -235 731 800] /Flags 4 /CharSet (/quotedbl/dollar/parenleft/parenright/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less/greater/B/C/E/G/H/I/P/S/bracketleft/backslash/bracketright/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/r/s/t/u/v/w/x/y/braceleft/braceright/asciitilde) /FontFile 19 0 R >> endobj 127 0 obj [525 0 525 0 0 0 525 525 0 0 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 0 525 0 0 0 525 525 0 525 0 525 525 525 0 0 0 0 0 0 525 0 0 525 0 0 0 0 0 0 0 525 525 525 0 0 0 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 525 0 525 525 525 525 525 525 525 525 0 525 0 525 525 ] endobj 126 0 obj << /Type /Encoding /Differences [ 0 /.notdef 34/quotedbl 35/.notdef 36/dollar 37/.notdef 40/parenleft/parenright 42/.notdef 44/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/less 61/.notdef 62/greater 63/.notdef 66/B/C 68/.notdef 69/E 70/.notdef 71/G/H/I 74/.notdef 80/P 81/.notdef 83/S 84/.notdef 91/bracketleft/backslash/bracketright 94/.notdef 97/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p 113/.notdef 114/r/s/t/u/v/w/x/y 122/.notdef 123/braceleft 124/.notdef 125/braceright/asciitilde 127/.notdef] >> endobj 16 0 obj << /Length1 1875 /Length2 8629 /Length3 532 /Length 9650 /Filter /FlateDecode >> stream xÚí—eX[Û֨ⅢŠÁÝ]Š»{q×âîÅZ ¸;)N)ŵH‹w—/ÝçœMÏþ~Þûë>7ù“w̱æxטsÍ$ÔäªÌb`3 4ØÁ…™…] ¡¤¡ÁÎ`gaC¥¦–€M]lÀ’¦.@;??;@ÌÕ ÀçàäàäB¥H€=!6VÖ.: úßI¼1{ ÄÆÜÔ dêb ´‡Îan h€Ím€.ž,1 þû g€:ÐqZ° ²³,lÌ]f@+TÖßFr–`ï¿Â®ŽÿrBœ¡R:¨$=ªhvy,€–¨¬Ê`h- Ôäÿ†Ô?'—v”MíOÿW—þ׸©½ Èóß`{GW  ¶Bþ™úø/9% …«ý?Gå\LA6æbV €í_!gi …ª‹¹5ÀÒä ü+t°ø§´q)°ª))Iéi3þ{EÿT5µqpÑôtü{ÚßÙ1ûCû±ñè³±°±±C¡ïÿ|2üG1)s°…tKpóL!SOTèÞ€7À›`ã`ô= Æ¬,`è%hS|–`êï…®9«å_±!mžŠ 'äúü4Ì e'W° Рô¯¥þ{„ÿß#ÿæb°:šB€  åQöGÿ‘ Ýlooúá°Z{:ZžB<Ðk¡UÀO!^«3ÈÔÙú)Â`õBÀO¨Øø7sC½\ܟƹ¡F.Öà¿v…<~·ÆÆí ¨®3tÑþf¨¬3ÐíWè±ÿ빡ª6Šðý¾gø‹øOcoóßQÎßý:ÿ>ž‚ЊbO­&þDÐJO-#ùDÐR/´ÒOí„ÌA» ûDP ¹'‚Þ¿üA«+>´ºÒA«+?´ºÊßÄ­®úDÐzêO­§ñDÐzšO½w­'‚V×~"hõ7?ÇÓ¦ý¯m= XŸö?TÒ쉠’æOÏÔÒâ„6 øþÞ+ TÛê„z[ÿPñ?86¨¹íu²û¡R<ŽlP+û'„1¬ Ô üB­ÿÀß;è„ZAþÀß›÷„Z¹üP+×?jåöB­Üÿ8Q V ÔÊó/ü߇¦¸8ØÃ›™‡ÀÌ}"ù¡Mçåæ÷ý¯múaÓÍd3ë…Xç$CÉsÑâ2h£­qêlVeby¤$I÷C_öQÿn¾*¤æÒ9L·¾[}wf0¯ŽtH+,¤–†“\3Í©$œ+ì»;fR"ü÷± ?Û0äfÆÅcp»+öÍ{ä¼w—/ã°Ì;¾î„Ç Õó/Nm×§uñ¹4l^Ýž%€%ŒŸ7í–5RIjÄ¥0·%étf²Ö.”ŠAaæjx8H5k8¢´š÷ÍMCô^âSr†V¾ÿ&Þ<—ý"ƒscEPìÝÜ3øa_qáQëÎ^xõäkgw¹€ƒeâU[Àshë¶*^…Ù]µ¦2‹Þ¤¬ä¸D£î…Vü†OÞ²,‘×V:{š"ÛKSGÑ …fé)ìñ…«ÈI$Šcæöü/ýõí=1ˆÕ rß÷ÔÑâZqì¬ybÀÔâѱS¢Ë4¡Ó«)­øÀsïSFãvœÛH`½ýtÉ—º›¬ëŽ´oÞïé×J,½CXV³Ð‘ïT‡7ªŠah1r»~=òyÓ–KÜWH M‚“î]Àuç<±Ê©@Hx‡•¡‡ Ì„fçÆkrãƒhDR™=¢í¶Og›óm|Hz©¨ Èñ…çVñ‹Ù®±ƒIù¢—½1t¼°S”øA#‹VëÓén|l.«E1’\f°µÎÇîŸê½îNl©ÄŠ*S}£?ÄŒÓc!Rñ±û›«ÚÅ<;îÑæYžßš^ÓÉÉcÓ‰E†Ó–i#_ƒçÒ#Eo_?ä ØØ¯¯ÇtÍl+$±»Þñ=ö2d¡µá” §F§unªysƒÄ0ÚÞ™ ߪ#bÜ^vòm³!TÄ“Ë*"ø‹a~’V8ˆ$; ë3`aºc:#”4KE-ÎÙÆ{ãa£E¡¡;°¥*`vù•8ºÅ‹è×5 BÐß{CÈ#غðýN©}\±Ø>¡ßÈÇ&L×>Ú1¥«Ÿù±¯îˆ}ïçŽU“¸ªz{¥M—]x}ÇÍÓé+(‚RçÄœ{y‘‡vLŸx¡ô ½²¥.œIΚ¾9gûóêOÜkJdï9ú û~åí€Ì–›Þ›úÙÎUÊ+óç¤0íy¦°5ºq«Øæ…Zt€àk@÷o2Ê#?¸¿Ã{Å_íÓOÏå/=Ö• ©˜aÑ–†Rñ¨Ÿ3j` ß‹åÑÉLýÞøØ5€* ÅõvÑ Ÿ­1}Ë_@ãѫղ‚µñ*§d4¹YH:—¯¥a$½§ßŽ)GÍâ4Q}ŨÚò.®²w²ñÀŒâzJ¹›-oЮYö\ê§ Õn÷áIƒg Ò ŽÜ0™p¿6QâkîGÌ)û˜È¢®ŸþÞT‰Ÿ—Óôéw”£¦b¦&R~ Í­w–HØOj©óóˆ[kŠžè\òA²â­õ’]ÖQ›èÍÕ°_³úpÔ×γiD#WŒèÖ2•UΊè´ð- Å×Ì+R¦‰½ù~§ˆ>³x Ô vȤǭº…7 ÷+Dvå„J)Žîö 9¢·]¨-)éwJ z§>QV¸ÆRì,e7y˜IÎg?½³¯wÞ¶!}gÏݞЫºd^-ŸòKÍc-¬†¼(p, 5òÞðl¼’C1Y‹aaÔÏHß°·MÇP1¹j——üB£Ÿùv­‘'"U´d‚FÜÄÙVdŠsÕø‰àñPù#­^XpëXt§ÁÝÓ_00Í·$Lï³_i‘vdÞª9Kf†:÷j¥%qr.£„’a*igjØ”f{ƒã‰¶^ÄÒ»·o¾*Ƚ3 {›~¥¯¿©!RÙýÉO†0@}ìAõrd‡"%Î}Ý¥Gl“ÒöœŽµƒÿ¸Bû™Æ}C0׌HëAäTî™øÅ¶>$2—-y²°Ys:™ã*» JÓ_¢išùþ‹“ ›ÓbŽ˜õ«‚Ô0– ¥ ûÖ±yÙù¼Ý#XÔìw~²«áyÃBÃ3¦‚•­›Ó`þÖ%\²:¡åøC9ŸÏd¥á­-CZqm×°Ôâ—1º‚FTÌâ60PCÓÈü2DX—X^ˆ’ï–J°FŽ X¼×£j ©!pPzW™ð<ž~‚E°^æuÉ*<|µ@0’V |J'ŸN¥èÇbË&›óIJŒmKb C­L¢XZ¼7à›¸Ÿ§’'Þ×ê‹%®Kª<Ò /D›Ž:ËÝÐP–ãN3l£E ’^]££ò™ËH¡8Zbz«ÈÈç܆¿­“Vã©-Š(Nꟸâl‘{IöãCC,uzøD˜kK¯€iÿYÐ,7‘>žš|½³šóÖ‡A—Ä TÞX;fNÔKéñ=>L«‹““¢gîø·¯ˆÇáõ ¯^>z(ë¶¾~ÆÑ1¢Ž°Z¨yçÓÃGTÒÏø±CY'‘ ó ¹Ÿ¤÷£¼úû:…êónKÛ£² ©bgáï¸ÀZâkè…Á9±p¼É/ß&GBô{·ÌVúÎFß·õôÝê[Žªå’n—HTÄ ]SŸ!RìÉSîfÁ ànŸˆÐŠì‘óã xz)#c1y%O,~º³)Õt%Ü©|©¸B ÆA웫.j%cŽy ”Hxa]öµëqoã=Óí^]—xùŽoÿhŒ—G]Ø\8Õi5?ÐÑÖÀ“Û¶˜ÁO4FØ¥‹B‚¿rñì)Êür…Ǽ5@…ã~kY`¹W4n©WÐí¸€ƒ.‰Êj…‚ág5‚i/š(ás¶…Xæf©µ9s96)ïC"B¡_íabŸhLÑëÙõLüt|äÇfKߊÞ8œÌ˜³Ô,Ç[M—·jðçÚµ{=fÖîI8û¥;AìÌ~ëýSœ˜ŸoÛÊ>ׯ~@ j¬–~lýÈCd+P‘û‘䜲õÍ^â(QÅPù°Ú¤¡(Œ5;~^1ú”›«{›‚ûyüãx^‰Oؾªt¢?èfFávK9|Æ”Šm׊±0{´ä£È­é|¹ÛDz%£Ç­ÚÖb¡h·-Äö#DZ­¾ñý>×U"{‡ñh,¿4Þ£“i/?7®R<ìýi§^gÝy©$Eÿø gÅy,r;>ª•…Tö$[RÃÅÅÇìϦåðáB°x³ƒ]u¬Nçi±#$Ú~Qàw‹ÍÆG»°³ö¬û3Ùè‘Ó‹HÁ#rßsj³¨´Üôs>„Ä ;%§h3ªV„~+ThdrW°+Ûõ°»o«æ†c ØJO)¥¸÷Ѿ´hOu'`¬»“šõÐ`$ÁÑ kv5¨©øÀS±ö|µáçNÁ#4>·WžÌä-{»Ã´­Ï2T‡¾]D¯ÄØNö#]»PyBtÔ<àÓ’Ÿãù¸ÓÔk¾`´·\–!E\nʘÕãÉdå¿h‘ßAì°·"Á×·®‚E4PÿU?‹ …¸Áެ ×LǰÝQÈâÔÛPt Æ!9£0~†Ò÷Òàh6xBõPSˆ£Ú¼â¦3g˜5r3)DL: nÌ›ÊE!*‡ÌuÖ&=1ôæëßÔíóØÚïöú¶­tA7y‚´~Fcö_HþÛƒ4TÛ¡F2é3©”Øl¬b&“ !‰ — R˜«¦dë£KÀ¬k2ál 5.YB¹CJEÒÞi©·ûAõ+éYµt6*öý-6kJ…iOߨQ?Náûa4«å„–ê<™þD‡8$âæïìÃvP·l¢à²óÈ>ÂÇk_5Íy+¬nÌ"$C‡¼·ˆ!©É:Rýý¦)!4-pÉÊÏÛëu~Êou-_Ö6Pç×Y´~ÌX$ (,m3ß´DÚ¼–®‡Áží£ϤÍÒï3úd‘!¼âädËZLÆ­Ñ·ISð@¤]®M%'¤DXÆæuýLòÍt ¥ »½°}ƒª9\ÿ%šîyGn{ñ7Ç™ü¸ ûe ‡6· !µ€“á¾|ýze@B¨Ï’^‹ërýxm¯àÊîÖÁ¨ˆ×m˜ØÕÌôd¦¼°&ÊDn·$Þçb'ôðþ¡«ç†k™ÑŒø°ž´ÂžV®41Aù#%“¼ïæW λïÕ\âO¬ZÒ·«wutØ] _ÙeK#¸Ñw}® { qîû9µ„°üsø"a²ºÌ8˜Žq£ÐqÓ¬ÕMÐiÅ;{fœ@¶<ˆá™?ªå/¾ôûoΟk>Lƒ‹\•dN›Á±cG@Ù¥‡³‘Ék²ÓMêqôZþT?/5¾ ·¢Rºg3x—¡kšµÙÄNš} B‹Q¤Ê›¯6ˆÇòÏÇ ]òÜÉñÙfªùª=W\¾Ôófáô;Oð£Ã …({-•ZjËZ‘\ˆ&ÐC^ŒDãýÀ!²LÄýêw9 h‹Ów‹³Ui+ÍvX-/F#üÄÕk.°n¶…ÀÍ€ÔmÖÀ5¿mÒ§PRô¬\I8ß«Íà }s­ç½ <­4¾+x[¶ÈБÈ3JGJ~¢£^˺OÛœ#-]Ù†ùÜqQ_‡²œwåqµŽÇs›Ùâ#9Ñúóáð@ŠGéZ’³µ5ëlw’ ÝrÒ¸üËÁ³Ç±ƒã'«žSÔ­ ÓLÚJèýþäüÌÌ ŒÖü•ªñ†0Íw/²ý««š³Åž=÷.6cÕŸ 4iF‘®¡¼¼UÌМáèþø£+«Ð“ìpÉÌYwÓßéçk¬÷ìì¬[‡©fÿ¯*E8Âçâb¹Ø LíR‹’%Ôü:m½»<ȇvïÃšÛø”ïä8Âq£“qæαNxâhñ"³ÐÝ‚YÓ ×Zm9Œ|ùOEw±iß`1ƒˆ†½ªX¦›ˆæ‘v|_3æŠÚ½%‡Ü£ß†,•ë­RÃÑ—R Ö;~‹zOÐúäè쉣Áðzfį5 .ÛëLnÛZõ25']­¦Ë½ÕƲy9'~>>!feó¨:Æ6>ß1PŠñã‰ß3&Y•=~쪘Öà˜ª…ƒ‚—Xßže«x2NüB°·R+üHþ†êôVõù:‰Å—ðÇLV"s‚ת„³“Šêyäk*öögh×ýo)´ÜO×»¯5î‘ë¾r ñ˜=ZP–4Æ-ס_•ÉôGIÔ›8©ò¼&,šx;CÒ æi¾D?nç~9‹½W·:Uç-¼~ôAE‘i$6¿2ÑŠ°ÚóýÊr¥óKU‚Ô¯D|–¤k^”?ö,÷Š©œP÷GúxaüÆi&n¸""“X„=‚cOÎÕ0òp‰Èž™i•½6ƒ?ž8¯+êj ·e3 £Ì‰Š- 7ß[;í7ž}8åUPcTnªyÝÒþ2اó΀ÑåSO~ vÊ[²nñLMc‘¬Ë<óÆy…=Ñoï.Ü):^Çìã+ÑséÍýÀ(±èõߺ^ò;jIù†Ý¼œü¤ž+Q¸Ii5nF©‹Ç~‰vª"4)újÙÓ„ˆÿЦ,ìûHGàùù³¯ç± ¢ZbÖQ\¯‹{DT°ó²EÂiUÖl~ÐÑо('‡º«5ÎJ"–³úclB¶¾]¤lÔ›„»í`\ÆG9±üÜt¶Œ,…1OÓ¶«“F²%z2Zöõ*®šÏá0ƒDFvÒ ß´1ŽÏèJ·ˆã’v%cê2Óbu둪5»b|¾øŒ§<¹Ç®£mζY{KÜJþâlŠêhƒáº*/£üµ}ߘGÑ~®¥¤îhyבY/1Üî+›“p=k¯Äãcl™-ÖÚµ+ï']„ƒ¼M#çn“Øâè;•ù6sÜï3¯N.f]e à»’’ Ñxú¸=ªw;\W?ëéÓˆ`ã…DLüìJ.rzìùÀn/ÂTZÁ(Þþ"µÈ4挥Ø#Ë,¶-"¿ªãñ¥—|®\Ë,*¹¼áŸôMP¢=A…QàÛ/FBFû烒\[xºÉ¶ê¢a¬;9ÜèÛÛÎÖö#_â%tݱb“m‹¸­@¬ØçMÊ‹-½&Ìà{­ç³g÷4q}"Uþßÿ'Õ`×}¼&†^‘¨1RæáóU¢Yш«Ü, î™g”ª¤÷'/ÇÄžÏîõî7ÓŠ§~ÄןpœÙ—û«ê¢ÞŸÜù~¹§l94Ú´ðó~1±'Fœ‚3ˆ¶ tŒì 2ß],'½/+ywM]Ô>À¡+d¶‹aû´kÜéP·Î6œ·‹Ì^yýý…–­Uë@™Ó¾‚ɳCÊšRªÐ‹+Ü—rds¿Ï*pê¯Õñå ¿WjXHž‘¢ªvb‹ëɵ1åÏ lrG|uJÓPXª©.ãÖ[ýl‘m£j½ TÞgÊÝ÷-îk&îâç”R[ÜÞF¦>˜»=—à2‰ª /×ê£;z‹Z‡ ¶È…ð (Þ–›VŒW=»/TÖ¨úšø }®Èb<þ¢„³š¾¤¢N*–17ƒÙ¨î°ª*©å[”@Q=Ѳ® UÉÆ>ßS ƬNÔ%ç{ˆx~À+bµ|ëËÿÑ™QÚÚñ²Rݰ®3ö|xþ…û3õ»¹ æ@ ï$‰Š‚aåœØu>èBºCï/QµÓwg² ŸI?Ï+vS1ÇFi½‘„7èUYh4÷Ó#3Yê"]+ª¼s{©»ù<–“Ú.¿]Ëç,)áEìžÅ".—Už¬r¯Â£Þ›ª‡ÙiZ YðEXš|UXC ¾ÊÙv…L칉R‰Š ½axIßÕР`]l U yôr÷>î×C2óº#uÞÀ$›vªÑݰ¤n«ƒ£Ô.Vêß^eCò&S@U€4oÓáÇë¯èç`4G³ÜDo§5=?в¿; À  (;`µÞPÿ.ÓÝàXã‚?9A`‰‘Cdœ–-SE‚´ˆÎòõxÇ.L~—$ilKh™m’T$¬1.K™nó1C¿r€¢cp“ÜG‰-ËèªÊŸÿú™:åµ\ŒJ}ñbÎé ÷i=óÉæ÷†ž=¥”z=DabZŽ/â— ¼.²©ã¸w—®/Œb_MÒãbú¿ST³±£Ø`?¢1O4Ìt8j³Qh7™ƒ§Åa*Ù8ÉHØàhÆ"oàAka­‡‰D#9|z¯áQ\õ|‹öšIæxÈK¥ø9OjI/\ˆ_´«P¼ñæ£háûâ™êùÕûŠA•hÜæ5֥tŒùè·¹ŒCgɵFÆ£`+éˆih¤4Pg%ñZ2-¤ }Ü#˜ðJq2<ðÊao+ž¼aî ++!5ZÉp}w…Òæm öÁS%gd–{_DÏ FZÏ–QF³2I9ÔøN{¼T ökœ ;žºR¯øÎ?ÕˆÃbøù›ÝáÈÒ‰´üÕ‰~Õ"¸‹du¯|¼Ã Iak¡ÛÈÜ­­ÄÜF|±ç¶8uÕWNKÛ7É~öDѯ:ͳoí˜wùVÆÏ´RúÅÑ„v Ÿ›–Ø\-û7D\œ¹=÷¦’§²ÈDé@a¶·‚ÓÖï‡åœågú˜Õ[u“­H¶*TÎ×2~mø39Œã.Ž· sù8M&¾Ðóã*ÓÈXÇ{ç3q'¨6ú&úkáÏ•~„¤E6ÇÝ‹Ú)ÀÏ„±QÎ!,;F·ë5p0„M³5€i‚™ÿ´6Ž¡ÿá xDX8lÙ|âC_uÎÖlÃTÉö5í(Üpô‹a¡((åÀÏa9(kTä ½ߌnHÓÑ¥j´DÕüÚRÕ²íò- ñð¿î=·B5¶jþ/,Äg:ßp§b²6yd‡‡D ‡cÆÒgõÆ?èpêÿê\£Ê̲pÏ~ðìRsF¬'ù…Ü%¸qsÖ¨ý©×´…3ù–ýyÚBoé l¡™Ï¸3½öÎy-e7ü…Ñnt¨ š&‰Gœ¨Uò1þŒTÚ°+­O¡HÖ˜ìV. 3‰ûç]‚0¥n#ké[Úy/cíV¶´÷¹4fH‚´; õ‰‘¿£}ƽ™ƒÑ’冫ùAZq#—)‹+/OÓ¶nu‘"K„'UÑ>p“÷Žœ·­Z„*󘪮 Dm$ç˜F•±äÆ%G‹9ÊÄ·aá$ìÅ+ÍŽ³Š·%¦ÚêïO§åmõ–emùWÏ8ï{a[  }h%x¬ãóÓ#f¼ yîå“ѰTrOg.žÇ9xޝNÈEqUWdB51)?Ÿ[ãlsˆªWÀõí¡qç…óåX6C ºƒËªµ0š‡íó\=Š54Ö18&VßËû* ÓÀ-²]¿;‡¯ ¹éàÛÇ4‰Eät‘ïçŒIÙeêõ6…$yðØ£„Ì›­¡¤$eÓñ±s¯vîŽzÕ‚+±ê-–ÆÏ˜÷ÅjÃMÜk|-¹{$n dðz~ù–³QQ#ø'Sïlˆ‚”ËgR›¯ñZ&1¬<•á`‡Íú¹)&`n´Ë_*,(]@‰ÞŽ‘ ¥¡T}רŒe¨‘ºÊ‚߃b¯Òݰƒ«›â¨7yÂÎÄ<á˽|èë-_«ËW„ôïd4¾*´Ãô:Á³a,ÃÖa"­Fà–½¬§ÇýnÂñIV¨5nž¡[ÈWãksêüÞqúˆ,J3¬¶o. C¿‘1öåÚa’5u,-’½|0ØI"È—ƒ¯2¥?^ZCpŠ.„eÛ(U— =?õSžuA¯LzÄT”?g½M 8L‹•LÄÏ”ˆ¦œ o(wUQ‡ŠµœCì0!®ûËwQ¤´ÃŽPBXqvÉ2—¨=o64÷o¬Ç5¨5%7—{Â'yn¢pß8gÔ{‘߆XŠÚŠQÓ(œy9 o}Œ%Û MkîÚ/âã”o:·éy¿V½Ôĉ™é«àGy©¢”¦“2*¼{»éQè43d:`mˆ‹TÉЫ]~{1¸ìŒúiˆ á}+›Í·ú´½BbÒŸ©\Õ¯~þÜÀÕŽ½„Èä¼,IItÿÃʃrßÞéÓ“?ôÖ{–yàáþ:0EÑSÞÿºãìÔ¥x-n«žŠÃ 9I÷蟣fùAß)X˜‡²˜¶ÏhËÿ¸$XîªØøÎêp[„Þ°|†ax[VQæÁïÜ"âDذ)wƒ@ÖÀ¿a®iâSÂñt…¯;GŸO dšþ]cÖσ µƒÝOZ“#F˜•K&c“ÅÇs/V'ªØÝ•4vŒâÂå$¢m¤dâžñzS"¡·k)¼‰Š½Ó/¬äT/y4ríóµ7^SÕÓÝÐ÷Ÿq…áœò¬©ö‘_øägŠšªËXo« u•Å4Ïø¢ù‘\Y‘u Koq#ÜQΞ;ó ´Ø0Û×â1§.–N¨À?A‹&²É nôi7ªn‚Ñ0§—ult.–JO”*JöÊ´ðÌTÇùü ߣF-v¢ãVÓ9­åÜ,\rÌ{~/¼(ÚÕ†êÔÙÔäš1(‡þŽÚÀu½c×å‘õ»·¹nVùìÒQ˜£ù\xß}GqžÃ½LQr¿{à-âã&3·åàÖF=¼Ó/Óôv×ßmÇ|ЀXd¤ãwú:+dë`®,ÒרìåTr-È©ìĦçA3ñžsâÔ{ÊX5©}¸áÃÚâÖ_Ùæbaå…±˜«È^Ÿ)"ßšN~»«>¸v’)G¼E r|#´·Î‚Šü#LÊÇTœ€N¥¨FYB®¯ oŒ¸º`Þfƒhfb¼ç}<1¸j®ÉÖ)rð.—è~ÑV¬ÌmJÒÝóÈz„&å0ư Eö{Û+d"¥Ó ½(!*„ÇUSÍü™v™MšÕìÊÐùvÎé}‰$ o;IÄR¬Ò{<ú=¥ë0šìëÈ‘"üfCŸÎgÅ—ß1èænFt½“eãx óܵ#L§A“Ÿn0ȼOpÙêó+—úGU^‚üæÏt>†V\’cæÄ®íä,\ŸÂydéO½¹^ d*¦ä6 Z”ÛØ'ß8ƒ³¢¥_Ç/òwÀk*äi(òÕ3.lÉ®ŒÚ¦ v“<’çßÒ\ºYb9cf¿ÓVð«òÎx¯Ý ÐSVT±Eļ‡+YàÕùÄi‡E©„‘AY«‚É%? * q@ç*ƒ%±2ïCÿ:.LTûa_óe0sòmÏ]ñW Á©Xp‰/Aff©§ºd”Î8Ñš9¦tgR ¶m-“0ÚqÑO³Žng+ãZÙþ_¨ÿ‚ÿ'&0M!.`{SˆêÿÑ|lendstream endobj 17 0 obj << /Type /Font /Subtype /Type1 /Encoding 128 0 R /FirstChar 11 /LastChar 121 /Widths 129 0 R /BaseFont /QMMEZV+CMSS10 /FontDescriptor 15 0 R >> endobj 15 0 obj << /Ascent 694 /CapHeight 694 /Descent -194 /FontName /QMMEZV+CMSS10 /ItalicAngle 0 /StemV 78 /XHeight 444 /FontBBox [-61 -250 999 759] /Flags 4 /CharSet (/ff/fi/fl/ffi/quotedblright/quoteright/parenleft/parenright/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon/question/A/B/C/D/E/F/G/H/I/J/L/M/N/O/P/R/S/T/U/V/W/quotedblleft/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) /FontFile 16 0 R >> endobj 129 0 obj [583 536 536 814 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 500 0 0 0 0 278 389 389 0 0 278 333 278 500 500 500 500 500 500 500 500 500 500 500 278 278 0 0 0 472 0 667 667 639 722 597 569 667 708 278 472 0 542 875 708 736 639 0 646 556 681 688 667 944 0 0 0 0 500 0 0 0 0 481 517 444 517 444 306 500 517 239 267 489 239 794 517 500 517 517 342 383 361 517 461 683 461 461 ] endobj 128 0 obj << /Type /Encoding /Differences [ 0 /.notdef 11/ff/fi/fl/ffi 15/.notdef 34/quotedblright 35/.notdef 39/quoteright/parenleft/parenright 42/.notdef 44/comma/hyphen/period/slash/zero/one/two/three/four/five/six/seven/eight/nine/colon/semicolon 60/.notdef 63/question 64/.notdef 65/A/B/C/D/E/F/G/H/I/J 75/.notdef 76/L/M/N/O/P 81/.notdef 82/R/S/T/U/V/W 88/.notdef 92/quotedblleft 93/.notdef 97/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 122/.notdef] >> endobj 13 0 obj << /Length1 1149 /Length2 4872 /Length3 532 /Length 5594 /Filter /FlateDecode >> stream xÚí”g\“Ý–ÅiªR¢t‚tAJèMH/!„–@½WEDQ@:‚4Az“")Ò‹€Ò›H—&ß{ï«sïÇ™Oó›äKþçì³ÖzöÙOx®ß… +Úam‘ªX ^XTDT¨¬ƒ)™ˆ‚¢"`2 Ç;a1P8)•–*z8!„} ™[b2` PëêƒsrpÄù•~I]8'Ôã‘.  „aNH¼Pü:á4@º#qžH;€¨(ÐÎ Ú"œ0ЯL{,PòËv®ÿÚòDâÜ ¡€ü„@BD;,í´CÚ@ºX‚’ä#Ô¿‹«z Ñºp—_òÿìÓTÀ]œÐ>ÿ¬Áº¸zà‘8 Ö‰Ãü{©1òñ”°èÿ0ÒÀÃÑNEŒ ÿcÉÉ]ÕÉiw× pÚÃÑîȿ֑»@hÜ_@Ê0=c5¡¿ïô¯í»p' ÞÐÇõoá_õ±èo&tçä 4‹€Á¢„BÂ÷_¿,ÿÍNƒÀÚ9aC!.„ãppa:$ô:aìÞ@¤7!3HƒÅŽ M Úcq€_W*!)þZúIAJ¿IRù›$Á@êo‚4~áœöo’‚t~AEïo’"¨ÜýMB—~Aö›nA†¿‰Óèo"Lþ›ˆ¿IL°°ûE äH0qø .Ž ÁÆé”‚Ð ÁÈå7®„ù FØ?ðl® Á÷ŒÜÿ@Bñ áù<þÂÿœ-%%¬·Ÿ°¤(P"þ•I()øo…‰Áÿõâ&ô_lïD˜g$Ò‰LŒb²áΉ•‘*/ ÉÜÙ’óÂ{#Ê“eEÖgd†qUý†HŽúzg§"ºç›Zo@vöâs,•Šþ~0PÄ8} “@V Œb ±naEÒ™ÙÞBë’µ<Á¹àZ%´5ɵ?ëÙHV¢½**™Í²ù´`lôl7ôqn‚*ÉŽkñ9À2³N;yoe6¶çxC û;‘ÛyÕ"eŸñçKËçO¨išdéèL #Ér§E§ ü,u©.5qûͯªÜ+ÏÐ-Ád2ôã÷ÆG¡;O¾|]¡ÚïIˆh.Vàyå?’›Ñ3#Xrÿ§ \F’ÿˆëVM`ÝŠ: ²/.¿]`3“™a¦4¾Àte˜«Êà™’¿pÚõ1d~{(ï!¾v+[§G¸Œ›®û¶!5m:]˜mWW'ç'Ó÷/MÇø3ÛŸK{—£ûîCOŸl“çÆÑÛaÐÚ@a[ºc,¤Mþ¶Ô7Ñ—«ÉÖoC)jÜ¡í;!ùÃâùßpOÈ;§°ËvÎï¥ÈÿˆÑ%B]~k¬¹ÁeüÂ9-+©öí¨eîå²ñþ&[Ëæ¯á—X,>EÝqòc‹^Ú­{³oëÿ%Ñb'CNšôg ž4•ž™?~ÌoèiJŽÑgDtq’ ׳î×®¶7KßAj,U¥ã´Ÿºíñ´öŒå2r ôŸœòúÒF?¶/ÒŠ”‘/‘ ¯š^HÄ÷úùÇ”ð`’Ö|&~¢'Ò¦ÑvvÎ&Ã.#[ôÌ’'ÞÙ·§zîÈoÈs <8ê-}OxÖ=r`á/ö}Ÿe, Íãæ“8è@w«XÂnŧL\×Oÿ`oë«™AiÉÇ‘î¾é»æW±y{ޙɱ`Ô*·wPDÜHr‹8!Y8 [j¤9£JZ+%c^ˆqÊÖÄdò“ÜÀ³q{cл_í MÎ’ïÞÎŽp©î§xØJ·Êòà)¤_“Û”µƒäÅ)ËÏš$é\sp¶¿~fL=ÿñœ~ PH¶¤§~€ñ }ye^H0dG©cþ1úý•üSáp©róÌ%c0‘ÀOVŽr£ )˾.ëàtÙË''‚™Š,SvLGÓ'Q[(cÿø}ÕáÄËpU¯yù6ÊåeŠ¥ û¤’B>¬ãSçPjf…Ž.5ùuŽN¼]ºG“ze=/…0»WtÞ"v…*Q­ÎâaÙàƒàô¦q% ·;0÷ÄÑ‹-QÝÊY_T6üÜ ŽñÉÚnMÜlBªµËÅcKÁ¥ŸkËú÷fƒ’£îä¶Àfa´îaYFÛ¼±‚4ø]m·½i.>…ômÔRý£Ù•¯yƒ¬B¹Emôy«•Cj—¢¤„Ÿ¨ ]qÀ¹íØs‚'‹ÏJ€š½«}÷î> ü2kT¿b¦±TâydFAÏ{@ Òë(¨ª­[GúâÈnó =*dsÖF ĸxŠ GÑ`ÉâäÏ£†°‡o«s¶)š4÷ãuÌŸŠïtrWg ²ðß Pà 7¼“¾Šš¶Ü,S“õ çVhEŸu\ .3t3~YX=iÉù„ô²í¿_¡Å‹Í½øb8Zx‘®©Â¬E-mOh4õî¢Ï `|Ú•ºÌ#Ÿ¥À œ ì²>Ÿ&çX—Ïøê €³Vá—¨ ‘ß$Ê5ËÖ¶‚~—`¤ÖLj†P÷x^r¼é?¨{íUóð Ü±{)ø«ƒàÇP+%·­"ò´”¤Z6äR?¥ÛͤÚA¼&Øì]µVS_U€v°êú"²•ß©ŒÌ©ýÆá %T Gi†Úv`nyí8„uÒ‹I'DmŸ<Ä’„C),Ù>l´o¨Œ¾º=ÿìvD,²ÈaÈõTÿF©Ã9…ìá>7ÄèÉ*U–Ä™èQ‹Tàýø~îVæ8Jÿ&Œñ‹gïøSPôGË·•\_†V¥ï<¾Æx¤n½„G‚¤Ü¿9Ñ›½Ò0äóås,Ñ×¥töãÅi+QŒªÁ{K{¦´Zy›‰çî¶þ¡þ­µ—×€æ·a&Yð®‰Aj¨•å þ«uóœýv‡¦¿ø=õݲVnIòd£›Ò|ìöè³>L¬±·³ä¬ÜËñS:ª[a÷Úç¾Â» z%/7Ò(ELŸqr*a¨Ø'guò}И˅¦­}ˆo»vhÎûv9Muq×-%6“¤;Ûú`?,ŽŸ:#Úÿ|ÙA¼ˆWƒj«£¥åÙ¶½”½U4D{Úì ^ýõ_vUøçÝíz9?©Hfi·°ÃÆ…PÏÓg\gÁ’Ó™'xN211&Hz‚›iOß§ÀjjNx¼G´|¤‚e¬ÖqrÂÔr=Žûá§-÷˜o8i\SB#<§òE½×!­Gfüøý¢Å½í'ñ‡—7é\†µR=í·–­ #ůëÊfd›?…nDhFÍU›ÈɽsÆœoe:·_3?”èXlûºE6ó2ÃÞl©êSû+~ÆP£‹·5Žðþ¸ªÖîuš§…ÃÕFÓoÐôMO9¨(Õ¿™˜*w{-=œúfûly-Ö[Kâ»Üt'ªÌ\¯ºìu‹PLLV$Pcz‹îŽÆ˜]Ÿ¥Q"¹ÕBò£\|ƒÕ÷+ }lâý/HÔìÑüÔóáÉÆAJM_)Œ§wï|–±Õia¨B¾ª©µf[ÉHl¦ªå€Í^çw”9Ž)œ‰–RfJ®¤|@nJ7»˜cMÇmë²RÌ.1Œà¡ÿ ®¸ó!!‰®ôø1'=½.Î;Ac–®õŸ8_;™’¸It;2N5½ÛRBdãÃålÅH”›8Zaµrúݹ1ËÙã•_cS^HŠx½™D]xÇÿJB|`û>¸ñÇ[… M·‹"<È JÊ;/XѺ}¼ÕÃ̽µ÷ÊQé«ý‚ÏÕÙÆhG8Vög ­Û†–#6›½üJÔ¥­š` é¿â'…½yÚRw€Ï_!‚Ь+ëé~´Œ:ÌZn·ªj›\˜‰:kžu–^-5‰ƒj©ASÞ½ U¸êP·ÖþŠ)âf¯jªK"'ZåÏËëQGñ%÷3è³Мś Ô§ $°)J¨œ„„ @díÏÕb‡.-tµ§æ[±ŽhÁ’êëµÍêT3Òþš-²w×t‰ûV%G¬=5÷BÄBNwÛ& {£wI˜0³¯JõŒåàMì®c.HÁ,aµ¥ä>×ô¥˜coIÙO[s)<] –µNmÁLÑ?~aî{2Â7wl]yT'@óÈWW.ë†}a<õÍŸÃSÑ• ciÜITùÓ´&ˆÄ–!'†Cq`íswû¥L©?Õ Rº*­CÐÖɨÇé[쳫Åi —âRGdTr?xþxkÃTXT;¹Ý¸êm¤ÓËQÕ´âú~ÇAŽ‘†¤šoìU¹ ß^¸ýGžl[ÛoXh|Ò1^dÛq  " ÍV°šn&s¹]«Âêe^Ôkm±þptÙ/¨2:´("H5öuZ/ò,_*WÂþdae³<‰áË‚£~1Ù#Ÿ‡§ÒïôÊù>ö¤¹'3[ëó9¡ˆP0ÊX!Á øöñšîç{KwLw®ÖMõDQŸCRn^¾òihd3/y¡?£ãCªå\ è$k°¤Ý$ßÓ†a€dúê ]Ž.GúÕËF9¢ì/6%nkdå³~º´–(^~nl¼äÃ[`ÄbÎÏ£c¾(ßšDÂA®¾“×ÿl•XjŸ³¥‡Û7x2ÙÚ+DçyÏ´±t–ë~m|ûd“Öÿ”bÌx¬š9Ö…¤Pæ,Ì¡7½æÂ¥jNNJœln˜ÁÎ…oUÏð¦æ¬»CZ~ó ¤¶¦²­Fë%×Þ'œµ^2Ô;À’ÑGyïï½ q—K÷Ð$ê;êO¬ˆhßu› ü2|E‚ºŽ·Âkòhúr¾áÀøñö¤ÝSäYþ £NZ¡Éõ”·½‹cÂݬ°Ã-U€ß|cÿ3USÊ퉮ÈôÌ©g"«°©óçìE^áçGË$›Ñöe•îî½æ7É︹i¼\É£SŸyõ»åðût^Ç”ª¬Ï¼Î²a|Û 0 •« |_¥#Ïz£åX–¦7dá:‡+/¦”×J!W"F GoÔ(ÔEõ¬kÈ“6%“ ¢ó´d¹:¥3ÅQ;|,wXÐçg:ÛÖ×¶e ð©Š&^¤ªCéUj•’ðÎcß‘×_´ ~bbI³cR,„ØäºËUßlk¢¾u—câŽ{6²UlŒ|²BÖ¸ªK?‹µÆy¸qµ;ìÙn€rXF±{¬ Ú“²[°Û^çÊŽm¦ŸHlìM9Òu²¨Ü{7n²hlÖ•3ŠA,yHzŸÜµ ®l§7lÛV¥ÎU¨?²Z]׋¥éä UŒ0hž<)Ì4º"°ŠO÷˜‡¾’ί½µ¦í¤Ðjƒ?ŠÝ¾A5GB£sêVe"(”¬‹%ü¬H…Fªä,Èâv§ÎT¯z(©ó–xicIÛ§_—G¦‹éuvZ;=") ãpÕƒºZãh¤ bÈ?Áº§íòW5¿Ë¿‰¾¾ª7oK+Ôõl˼W–zÉ @M>^!FSTwI6ѵuûŸDÈ×G¡¶sà¬iQÖ~ëe²ÏÜ$Z9/püÈrÑú ÛvýƒÑ[h·HÏ/$ËœÈ;²ÞÀ”ï\%ÏêK°Ri3ÛºgX§ÊKfï¿.Ó¡ñѽ\R‹ b.‹Å²K7—Ð&. …¶ÕÊÝ{Vtwî Bá$UãUZ“M·– `‰‡KÂ4›»É㑉á…[aâ#¤QªÆ˜¼~(…Ãõàç™”îñ?)xÎd¿X`kËäÁ(4Eà/ ~W9“|#_½rµøKËÍ"¥£ÇSí*sdîíÍ!ã«®[E=E× §|Vg3å‘•pqpUnSå“”àÿáðÿÿ'h$‡ÇºÀq(ÀtsGendstream endobj 14 0 obj << /Type /Font /Subtype /Type1 /Encoding 130 0 R /FirstChar 65 /LastChar 117 /Widths 131 0 R /BaseFont /RCSOWG+CMSSBX10 /FontDescriptor 12 0 R >> endobj 12 0 obj << /Ascent 694 /CapHeight 694 /Descent -194 /FontName /RCSOWG+CMSSBX10 /ItalicAngle 0 /StemV 136 /XHeight 458 /FontBBox [-71 -250 1099 780] /Flags 4 /CharSet (/A/B/E/F/I/L/M/O/P/R/S/T/U/a/c/d/e/g/h/i/l/m/n/o/p/r/s/t/u) /FontFile 13 0 R >> endobj 131 0 obj [733 733 0 0 642 611 0 0 331 0 0 581 978 0 794 703 0 703 611 733 764 0 0 0 0 0 0 0 0 0 0 0 525 0 489 561 511 0 550 561 256 0 0 256 867 561 550 561 0 372 422 404 561 ] endobj 130 0 obj << /Type /Encoding /Differences [ 0 /.notdef 65/A/B 67/.notdef 69/E/F 71/.notdef 73/I 74/.notdef 76/L/M 78/.notdef 79/O/P 81/.notdef 82/R/S/T/U 86/.notdef 97/a 98/.notdef 99/c/d/e 102/.notdef 103/g/h/i 106/.notdef 108/l/m/n/o/p 113/.notdef 114/r/s/t/u 118/.notdef] >> endobj 22 0 obj << /Type /Pages /Count 6 /Parent 132 0 R /Kids [6 0 R 26 0 R 33 0 R 39 0 R 45 0 R 51 0 R] >> endobj 60 0 obj << /Type /Pages /Count 6 /Parent 132 0 R /Kids [57 0 R 64 0 R 69 0 R 80 0 R 92 0 R 98 0 R] >> endobj 107 0 obj << /Type /Pages /Count 3 /Parent 132 0 R /Kids [103 0 R 109 0 R 114 0 R] >> endobj 132 0 obj << /Type /Pages /Count 15 /Kids [22 0 R 60 0 R 107 0 R] >> endobj 133 0 obj << /Names [(Doc-Start) 11 0 R (UNDEFINED) 23 0 R (page.1) 10 0 R (page.2) 28 0 R (page.3) 71 0 R (page.4) 82 0 R (page.5) 94 0 R (page.6) 100 0 R (page.7) 105 0 R (page.8) 111 0 R (page.9) 116 0 R] /Limits [(Doc-Start) (page.9)] >> endobj 134 0 obj << /Kids [133 0 R] >> endobj 135 0 obj << /Dests 134 0 R >> endobj 136 0 obj << /Type /Catalog /Pages 132 0 R /Names 135 0 R /PageMode /None /OpenAction 5 0 R >> endobj 137 0 obj << /Author()/Title()/Subject()/Creator(LaTeX with hyperref package)/Producer(pdfeTeX-1.21a)/Keywords() /CreationDate (D:20091110083250Z) /PTEX.Fullbanner (This is pdfeTeX, Version 3.141592-1.21a-2.2 (Web2C 7.5.4) kpathsea version 3.5.4) >> endobj xref 0 138 0000000001 65535 f 0000000002 00000 f 0000000003 00000 f 0000000004 00000 f 0000000000 00000 f 0000000009 00000 n 0000000883 00000 n 0000001007 00000 n 0000416555 00000 n 0000000057 00000 n 0000416446 00000 n 0000416500 00000 n 0000818805 00000 n 0000812929 00000 n 0000818643 00000 n 0000811613 00000 n 0000801683 00000 n 0000811453 00000 n 0000800336 00000 n 0000788808 00000 n 0000800176 00000 n 0000416297 00000 n 0000819529 00000 n 0000776022 00000 n 0000417489 00000 n 0000442771 00000 n 0000417362 00000 n 0000416682 00000 n 0000442716 00000 n 0000442567 00000 n 0000441709 00000 n 0000443784 00000 n 0000473393 00000 n 0000443657 00000 n 0000442896 00000 n 0000473244 00000 n 0000472386 00000 n 0000474506 00000 n 0000517428 00000 n 0000474379 00000 n 0000473518 00000 n 0000517279 00000 n 0000516421 00000 n 0000518580 00000 n 0000573952 00000 n 0000518453 00000 n 0000517553 00000 n 0000573803 00000 n 0000572945 00000 n 0000575185 00000 n 0000632161 00000 n 0000575058 00000 n 0000574077 00000 n 0000632012 00000 n 0000631154 00000 n 0000633518 00000 n 0000693483 00000 n 0000633391 00000 n 0000632286 00000 n 0000693334 00000 n 0000819638 00000 n 0000692476 00000 n 0000694905 00000 n 0000756340 00000 n 0000694778 00000 n 0000693608 00000 n 0000756191 00000 n 0000755333 00000 n 0000758186 00000 n 0000757855 00000 n 0000756465 00000 n 0000758131 00000 n 0000788416 00000 n 0000786865 00000 n 0000788258 00000 n 0000786537 00000 n 0000784673 00000 n 0000786379 00000 n 0000757982 00000 n 0000760899 00000 n 0000760568 00000 n 0000758292 00000 n 0000760844 00000 n 0000783869 00000 n 0000779295 00000 n 0000783708 00000 n 0000778935 00000 n 0000776051 00000 n 0000778772 00000 n 0000760695 00000 n 0000762227 00000 n 0000768861 00000 n 0000762100 00000 n 0000761041 00000 n 0000768806 00000 n 0000768657 00000 n 0000767817 00000 n 0000771436 00000 n 0000771102 00000 n 0000769010 00000 n 0000771380 00000 n 0000771230 00000 n 0000773159 00000 n 0000772820 00000 n 0000771566 00000 n 0000773102 00000 n 0000772952 00000 n 0000819748 00000 n 0000774591 00000 n 0000774252 00000 n 0000773254 00000 n 0000774534 00000 n 0000774384 00000 n 0000775927 00000 n 0000775588 00000 n 0000774710 00000 n 0000775870 00000 n 0000775720 00000 n 0000779179 00000 n 0000779149 00000 n 0000784378 00000 n 0000784146 00000 n 0000786771 00000 n 0000786747 00000 n 0000788696 00000 n 0000788633 00000 n 0000801127 00000 n 0000800801 00000 n 0000812457 00000 n 0000812071 00000 n 0000819244 00000 n 0000819060 00000 n 0000819841 00000 n 0000819917 00000 n 0000820166 00000 n 0000820205 00000 n 0000820243 00000 n 0000820346 00000 n trailer << /Size 138 /Root 136 0 R /Info 137 0 R /ID [ ] >> startxref 820603 %%EOF jsamp-jsamp-1.3.7/src/site/resources/css/000077500000000000000000000000001356450004300203015ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/site/resources/css/site.css000066400000000000000000000005601356450004300217600ustar00rootroot00000000000000/* For some reason, the maven-theme.css file that maven puts in place * includes "p {font-size: small;}". This is just stupid as far as I * can see (presumably happened to look good on Mr Maven's screen/browser * combination), and it looks terrible when P and UL/OL/DL elements * appear adjacently. So, reset it to normality here. */ p { font-size: inherit; } jsamp-jsamp-1.3.7/src/site/resources/images/000077500000000000000000000000001356450004300207565ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/site/resources/images/HubMonitor.png000066400000000000000000000425741356450004300235660ustar00rootroot00000000000000‰PNG  IHDRÒÔ[>|bKGDÿÿÿ ½§“ pHYs ‰ ‰7ÉË­tIMEÛ %& †C2 IDATxÚíyxUÛÆŸIÓ6MiK)”­–µÈ"(md}‘]DЊ‚€(hEQQ_×EáC¤uCADqgSi°È&;héM—´É÷G4³ef2ÉL’ûwqqM¦gΜ99sçžgÎ<Ìх 3-˜7MÛJ'Nž­yHü¤c'Ï6ù£R|a€`×\ÿUkö|Îß¿Nó}ø£N$šë˜ } ÄÌù¼ 'ßà.†&:¶`Þ4¶e6¹­šëÅ튕SÚVÌdè¨!©Ap=f2BÍc¼³Äf¾ß¿ÃL€¾b@3ã{5 ]{/ö,oÛxw×Þ‹·m¼[¬˜ÄŸ<5È)À^ÃÆëæì2bëe©? ¨èvë@L 3®ænÛx·[s%4QìOò…ÃS’½‰{×2ë—³ìuïª h¥¹@v¡¹wK«‰„{-)s[iåÕ÷" 3ÈáÝiÝîfé2'à"Èx<¯Øz±(PÚÏÐ\ &q^È®qm¯Ä»R-`׿^˜šðãÔ2'­Vt ‚{á\Iˆ}\Ãi˜×ú&>¼újEîÈ7\]{/–GwIé³Ú·˜8ÛJKªØŠ¢x$RP^ù+¦õÒÁnéú¡¶A+¼vÉr»˜¦cH/ bþNúTWdrMÐÊ8³ULâp¤DfçHOÞPz°‚­…ò†|¬A†ô¼ñ•˜0 mŠ•NêR*>@ÔýA×àX'Nž=1«u06²Lâë»á žÁm.Ô`™ÑEÐKº‹žòÝhP;Ä9 I” H­®\·‹ÛeV^¥7Ê0“ Eè«iÞe7¨UB@yÙ·ÔT_\rƘ&Á±¯"X@“Ÿ"÷—â¿úAø8e z)ížä$¨¹üùOü‚ N;œ¶åUÅ .‹Í!ãOé›¶¡è@Øèµ°XòwÄynPQý(2€k® {’0€Jý£¢ú•ºQ9ë#³Ur"¼*ªRt¤¾w çèd@ dÊßAÁ÷OøÅÀÌ3€¾b„8kÓ\#¼Œ΄êKÕ˜‘#ºpÞŽ9qòìØ¸øÊŸÈÊÊ\Ÿ““ƒÎ¡Dvvö¨;»òÖ,&Ìb羑‘‘——§T‘%ÿþ¯ì"»#ø ¶Àb&È.Л͆N]§¹YYÙè Ù @š‹~] ¹@v² €/äææ¢@Ø¢YâGö-iÏIå^éþÈ^ ++#''O“ñ¦z\`LbØÉ.glÙl6þ8S=ò0p¡¼Ò®Ùf³ 9ßM·Š±ÇÙãøEvùCSp¨ñ¥YÌó=2GÊ5Råõ“ÁäŒ Aw,6&Ùã=öØC”]@b¸JïBbäƒFŸØ® °JWþuƒ¯0Ø‘HÑ ÕÐb¯zЉ9VÏe–cr9ÍùnWÊ2h‹Ì:9•©D«á!1ê­ûòZ•Ÿ~ìùÕbT‡êh7ëu.©Užk1öÛk4´• ±ï—s¥ïËÞÇU€30útízN S(Œ”ðeÁ suCKé¸B(¨Ãäq5ÄXv|œÃà'¹Ç¸Á!»tù<ùÑ1Oyù…9[á$ò’дÎÐò¬”¾¥ÆWœ±'m8ø ɽa‹fKŽ!‰YeÒ{% c°†˜òª›C¦t„(Ÿ2Ëø>ì1¼!»é×üø#¼i]¾dgg@s!µÀP }Ͳ 4@v ¹¹¹Ð\ÙTÞRÃk°±è“ðAúw&Þ={–/_^RRÒ°aÃÑ£G7jÔ(** _ @Í öó„䯩¿d×n·_uÕU£3͘`ªKGþ Áž>qÚ¼qÓŽôôt|a ///'''++ ] íc»UUUýû÷oÖ4þ÷?;-">Ž1™˜´æÌ¯["Š;¸Ï† .à&++ @v5`×®]l\0ËÁýÃ0 ^¢©S§Ê¬Ša†aÄ>ÊÙ…çE?€°Bã CyyùíÃËs™L Ã0gÏ»–¬tѨaLÃú Ù®1ÙÚn>uêTJJ z?œñÄvÅ%ÚË8£îͪ¯D!ŸŸ‚Cr¸]õ¬_¿þãœâˆˆ¿-gënÎiϹ¦=çjÔÞ¹éG—»ÌÿfšV¬XáãŽØ®VÐáÂö†°Ï{/Ž/U©ÞÖ÷–¨{ÿ&€ìþÍŽ;:±n˜•^üwùö{î…¨(fëÖ­~=*†a\.—{ß±aÑ*¶Ëβ(˜qQ"¿(çO6Šô×£¼^·å×ÍEÁ'ªªªÄ„®¤ôï—Ëåp8i¨Òf¸5‹çõë|Ï[~½šÊ\éÃØíe·I“&N'yn¦EERÕ?{û&R“&MTh(¬k(¡.¶ËÖG™`™ƒª‚@ ôõ¦æžM¦zIdŽ ÛÓ‡ ÿãO¿t 6 ]Ÿëcl »DDõë×ÏùÀåñ§­ZЦµ¦_›ræ˜<Öuî"W‡´Ú#,pð‚y»²«‰‰‰ý-X²ÊY]í""“‰i{%sM{&±6ãÖÜù9Î1÷¾””ä㎯oQÀ²KD&L8^øüU½Èw²×ï?è;ÙYûŠÅãÆ“¯­lÝûÈ^ïº|Á†Åãs}¿Ÿ&ý†Jù¯ŒTôfURõ6UÏÍ=þô Hvø }N“Éôä“ONŸ>ýÓO?½oúkûöí«©©iذaNNÎ{õF¶ÏUª¹^_4éã[)%ÔSæ‹&eŠ»¢MdW^½fsfffff&ºˆáŽí"G@€@{^ô€Û•s¶ ë€Oø2o€p”]i†¾ÞìüGgú6éÚ­ÉkܹKäØûínÇî:©1Ÿe¡Ó…_¾]ÄR€¿d÷¡‘½J.]jìªEË&mšu·F«S÷RùÙcç÷“…icGáÛÅôdàGÙ=r8¯ÄR=ôºa'ŽžXðÍ«—ý­‚Z8nÏ}åùûç}°øwt= ¼c@v}§ˆªÊK-÷ßó[î×{=+MÓÐë†õŽï¼åÔÎI'ùiq%ú(Šíb6+€ìr™øõàA{;ÆØ#fzì̆-ÌÕ³;¢jчÛ>!¥çµ}jŽÄ®8Y¾m‡Æ^èfpy»n×'ϨýÐ;7ÆÔ2Qçn1±QEŽŠÛ¶å—õë³»ÌSïéÝ÷ªC¿ùÍèkë;õ:`›áØdŸ @ðÉnÁ÷åË_ø‰ˆ*.öº'ó‰J{¥=¢°kWÛgŸ¯;nL¹Ã^\R4øú̧f¾qæÒ‘ÞÃË4·ºò5¾Ø8 ß.€ìú^¨ìÒÍID5–OLŸm6]²Ñ”§ÇöêÓ§Ò^IDn¸iÕÊ%«ÞûºõÕNô;€ç]_‰+³<î2UºŸ¹zêô’óóó ;b"­µ7üømqIQaqñ¤‡&|¶ô’Ì:=÷¾Ø.•ó‘=àÜ%s¯çÔÀ7¿> óí\hvKmÙÚ?{§Å*cü¥^ÅDÔ aý´kkOKéc«2w8¬¸¤¨ÿ½=•†8Ú*ü¥Ìb 0>š àv}â‹í+­mr<ÿΓ1‘ÖûîËêÝ-?''õù|í¶•¿å[—¼p=< ¶ àv5ãÇÍåìcÇ.ðë1À«Âó¦nù ¶ àv•ž3è: f`ás€Û†±]·ëw& |Öuìh‰óôqgY“Œ!¥E«×L³X­øÂØÃóÈ®ÔýÑ+ ÿš»ñÅ7¿øï¿ܺsq m™·ìÌgøÂäda‹ƒ vûð¶wÎh;nõ7Ï-úí}&¾ÎwæŒ_;?x‡µm…ÝþfþŽD&¹ÉYU1 Ã0Œô™›+ÚøÛçªÞÜf³ñsŸ ®ôÍ+À²;¨ñÀ÷~·üÀ²'žê³&µY¬Ö¾Õyûò~¬ãªe±Zí¥®k2fUÚêº\.— Î ï±]¶&ÊÑGè2 Éó%iS?Nw–ïJi:6ïåÂmEñ:·µž¯°Û×~eDõî »ÝUZÓ å.Ú_‡,o›#‰È4|¡L¯êr¹Ü.UP1Ùî•]@ÐÕºWº‹y–Ù+Aà=¯Ÿâ At¿ŸÂóÑf³qÖÐåï°TR~aù ½ÛM­ŸP¹ä¾mË&9_Ë,:Û䓪CmïÌÓo[¬Ö‡#ìŽêüc|˜)‘æÈ‹¥%…ŽŠqiwQå’û44°l%eKªW1õ¨9‚Ä÷y»¡äÈœ[Ùjè)À^pÃÑPÏŸ8»ã–Y!~q»n"Í‘D´üÀ²HóÊÌ× =µžˆ ­z« }Ús;"Š‹O ¢E¿½ÿŽy™|«ëï°Æ‘}.G¿øòjp_ ý  BÍd×­¹DTa·Ñ]´ŸRš¹×<;iÚ«¹ß‘éúçœ_?­´f¶ •gÁ‚üw©©V¯’§He†µj<”þ²jd¨°Û[ÞöÚKÔÊQí ¢¨Ý¬ˆÞš¯rT;ÕŽâÔëmu—£ÚQùÅŒõ·Ýí.ãþ_ŽÕeÇ$t¦5x=¯CÂð²¯úÅΙ²(§°¢ AØ¢ìZ¬ÖÚ&ס´KÛ¶X­#ªw¹&õªæ)3ºÜ×õÎùs?<ëÀ;î’7,+ÀG#lLŒŸ“Á`hÙ%¢mË&½—1xìnоÿãžy·çͽ×Ì1ßÒwÛ²IêžCãÇÄn|‰ÝL£€„†.>—ãg%Œ0{Ž{% MHpÐÒ…%*€ƒ–·ÔJrFZ¬Ö »ý÷Ÿ_ÿåéFW¶zþñVªk”KÏJAE–SXÎ2 òc»bª*½Ò÷2ZU€_Ü®;Ôàþ¿C¿îç^½ æySÙ@>È· »ÀçÙ! òíÈ.ð¼­ì^׳ï«ÿ—}àøÙ#ç /Øk¶nßµú“Ïn2 =Ü ¶ ¿¤9ïÚµûƒ?283³šÈb¦¥wJp¥Ÿ?{n䈻–~ôúà½Àh(½ðR=zµ—ÝÁ7ßzÿC“›¥µ**«¬“]CÄ0D QÞý"££Æ»ïwd=«ÆyêWÎCÀbe×ûòT1žHö_æíಳ³åûÕ£W{Ù]óvlBBtLDö’Oš§µš7þo¿î{uöËñ֘믳µjÛlðà›ôêSöCk>>4ÌOì †ç¡1&eZcùÍAãØnæðQ§Îœ‰‰ø«¤")¹~f¯ž[Ö¬ùëäÉ1·fþYpáBI5ß°®K¾½þÓ-ýZ[Ù¥Ââââ„hsÙÅÂ÷>Y«Hs¥s™{Íb.±­œú¥+—Ù6¯+áyUÌÛeß¶2š¤|w@żæ˜ýT¯ ¼>2å!|—Àgƒ"ÍœBàVa¶ùUtÂp¦È×}{älµ OÌè\>Wæz±[[Ò%åo¨zx @âG È· »èãyõÚ;îk@¡.¶«•Ö£ÿà¨?^¾ P¡Ùzú\½òà ¸ǤüéËUdèòícН¿wØ.Ðßó¢@XÙú€|»ÀøƒSb² àsÐrpzB bË]”è>o×ß)iò&¨ ¯gpr–!»ž×'Md+£íøeøùÆ sž†ò†¤ç…ì‚ vÏ«KÜJpR’»áˆ)={¥X1Ný §˜ç…ìø\¿«³VÅ@Pû\ÄvAÐb9c x /b»ž×èš‹äåaây!» ˆÝ„Çó\Oñe…íàDlÀçj ÿåên8ÙrùëÙ+×ð_ÞBÀçjåy‘“è†r2¨~µ°×téÒuã^Ïàä,Ãíx^êy!» ˆÝ„Çó¶‘ðªa>8Ûð¹èésÛAޱ]$ù^ ¯ÿb»] ¿ç pœ¯–r~ŒÝÞÖ=ZØË2€ vÏ‹ÞÆœˆí‚ô¹è`ÌÁ‰œ 4 ®œ ~Ê· ¢Zd~ŒáENÏšÈIãÀÑYM2?¨Öîð}ÿÍÛÅ-5 ››pc¥±]Ž ®!¡Çs9™pùeµLLbØÕJ´¿’½SήÛÃIòË.æu§b‡Æ¯“Dü ùiˆ%ú-4§Ø|¸]F>×sž³û#ÿœLäeø ËÞàzŽ„ñÛ&¸’Ó0þN…OâWÁëásÖóëT¾@K7&ä}.b» èQÛ•‘«:å‚/ïJæ¿öøX•GyÅ~$B8Ñ%b»žWêÊt 8ŠYÈà }âNÏ ÙAì&<žWƒã_Ýøø¡ƒ`W^dj¥¤éS6]>×a¾ã òêÞ6šty±¨±Ž!d膊Ø.;•8çöÇüª#±ºüÈ&;èÉÞ¯DÛä+”t%b-Tjù7ädjìa‰Æ™`ÐßóÊ3È¿-&v3Íkzr±ÒÅä·M]=‡ ¿¼¢–H| á(D`f‘Cv>¨ž· €ÿl@`›„ì‚àð¹þÀh® ÷² büí‹!»@7ôÊ·‹d»@_ » =/Rî1“è|)‡Ø.7 »@OŸ‹~]GpåÛ@+Ûú{^]â ‚³ý•Î%ËããóÊò³JjÕÆ™D¡Õaê  »Àè>o—ŸÒ *&˜¬Ró6óB3ˆæÊlŒ‘U² àsµ÷›'<ÿÉZþV9Âù:ëU_äÔ/˜ÂœWEk¥{CÑQ{URéö«hŒ_¯ »À¸è5oWß”0¡-VRf šXcée15÷Zƒ`o¨h•¢Ã÷Ú~鎕ómÂíxÞÐGæÊ±Ûü¾ë»VfP]=ŠADAN­HŽYÓÜÐøEj^Û\éÐ »>7ijŸ.¨U„ 4 ¤ìýæíÝÀ¼] É”p;öÀ ‹LÃíx^©óŸ=ËJ~hUº* 7áH•Xî]¾” ÞU“¿kébêúJìp¼¶_ú·JúÛÔEë!»@ŒÛõš&\b¥œbò¢«h’Ìú¥+”Ÿ^þT_ù‰ç5ù:TìNÇËÈ.€Ïa‡Ž“v!»@O ;o„<úFÌqK èïyÑ ¬€Ûú ol@vA˜ú\]4³Ö€¾?Æ2ÝÀ¼]ž@vþžý »îJßÈsÈl6›Ž›ê»wÙð¹:à~ _ÐÜRº¡nÞ.¢»àÔwþ£Ÿü¤Yœ2‚ùgùå•îZºZÁeÎã­Ìõàv<¯°`±}(û#çª\,ŵ`é+zþ®ÙkTW+¸#(,díñ%¶+_Èääð“Æ©®V0½„² @ }®à…<[ˆq÷ @vð‚ºy»|åEè@vçõ~2΂ÕÊܼžW¯½cb@„:æÛE²]ÙáësõʃƒàÐñ÷A ˆí‚ð² ô÷¼èÙ p—ùxg;€ì.>×fËX;ïË­5ÎíW¯æAvAcÌØ.ûdÞÛPÝè'rsƒuF f2ý=¯¾q·@¸Ïa÷²Í–‘››Ç^æ¬dŸóžì ¥×ˆUÂn‰XIÁÂr*”_¹ ¨‰­,Àé:E‡,¿—¤ë÷Úx¯GÙ¡†ŽóvѽÀ>™9'6_G<ÅÔíQl™¿À7^+áÔÃ_ÁÊ¥÷(¶ ±ß¾&z]VÚKÒ¿s^«’ÞAÊ>W÷f(:÷49QÅ*‘®œóW9-áè /íôñÀU²º^ –XÜ.Ð uïR  2VÔ éÂÒá²¾{‡ì‚ ô¼¡4‡LN`W,t`X§/§Íï8d}÷Ž ‚qÞ®Ž'møÌ3¦?…Ûð¹þõ€bwÕdÎdyEìusÎî$l¦Š›òœ ÙÇëuïž øÝ(}È‚7åd”ô—"¸w~ý6[FÀ#dè†î±]±»Fw“ø @é5^—5¯Pé†>VåµcŦˆˆm¢¨r‰2»Å‹ Ðßó¢@X· ôÁ óvÕYcùe‚÷Iª°–ð] §ÏE`4ÙÍÎΖSrüøñ]|èÛÅ &€Žc² ÂÎó"·:Ð÷Ç·Ô€nWsÏ‹ÞadèésѲ @à®w©Ùl6›ÍJýÈ# ½ÞóÄvþžqMtˆrsså—‘. Ä~'|ï7È.ÐÕóvÙ'ƒûà¯aKŒç¯že±2‚§“ع'¸SþzvÍ‚ËÍc×&ÝfO1›Í&Ö'be85ËÙ˜”‹ ÿˆ‚BñÙߎ†š‹ ÐÙçªötnʼ©-Q¿œ&yÝ‹ü‚-T´/±òrºHf7†ÒEd Ë· ÂäòKfâGÈ.}ŸKD mH†ŒÓ,›ˆrrB2 $2[*Ö\ T2È.ÐÙ\(èòÈøñaúÌq^^^^^’[fddÈïw,ñ#d™çUtn„s:ó0WÞœœœììl™À]8`mÃL ³(À“²(–Áì=}n ¯»Ãg@vFÃ|»^§µ²#}!¼ŒAåã8ìx^-Ï%ø\`å…ì}Ð*¶+ç, Ÿ «DÊ Ùð¹ð¼P^È.ù.5Ävq€ì‚ ö¼Šð‡ÏêFd„ZÅvåœ?ˆíãh.d«ÏUt!¶ Œ£¹] 'Æv½žKxP aÒNBvAˆx^‚¤Âú .ß®™ÎsAÇïÙøñã!»pT‘æ*ÊB ÑAÌÁ°™?!»@ϳBi’Sù…!:À°÷Ûú{^ô+àvn—Àòc»xÅ7€ì Ï•£¹x81dºÈœ @vø×ó¢d¿Èw©Ù>@v8ˆíÈ.ð¼øL ú 8oæ@vð¯ÏåÜOCÆXbÞ² Œ…œœ îçÓŸ˜{t WƒƒØ.ÐßóJhnVVvVVvNNž ¼ºÿÊþçr¹½–±lœ4ó] óõ—ؼ]·æz> *oVÖe=;;‹íÙšŽe,rYfäˆ. æM#¢üýëˆhAN>ÅÆu<l6›û ·>€„e¾Úz”‘Ûeäh®˜Î²™£¹‚{Ä2–¼<êή|…Ûº!1o7777;;‹ÿO°|VV†˜æÂÇ× IDATaÙ€žnèæv9&WéË&0~$ nSl€PÈ€n¦˜ooñÄ%ÄÞûÙºÁŸ· ç €þžý »øÄvdø\ » ¤A¾]ÙžÈ.QÛ]às€ì‚±]žàq  ¿çUg`ç8÷š×\"Wº TÔ¦md5¾KMЉ¥ÝñröÅi!@vAû\ d‹ Áä¯tÍœ•ž­Üú.¸þJöïÿwB¢ñ2 ‚ÄvnøÛ{—šDy¾,º…ÌÄJiO-&¯¯€óTÎ9 ™p»øÑór„É­>b¦^ESulAC5”ÙH¥?9Àß(] Šb»ü1Ͷ„ªc»lù6¬‘l$l¯¡Pú+ˆ ÐÓçÒbH×`dÿ]¨ŽízUEòÎö6”@èïy•Îgð\ws$ɽž=ß@P‹¥gx*‘¯wüÂìJ85(­Ó »hƒ/óv%H0úéµ°ôzébõH4@QyH-‚ hésÑn€ÀÁ—ZÐ+ àvÈ.io„'¸¥ô÷¼2ã ÐhàWƯÔ7@vA0áã¼]üAvv¶|Ç zdÏ #SN±œœùͱ] ˆí‚ð² ô÷¼è†a†A?@vð#˜·ËÆår¹\.ôdƒú\†…ض…dÿIºŒœ}Il%³6‰ ¥+a.£(0°3ÆAvAp£.¶ë–×?°Ý¢û#_ª<Å<ËbeøZ&§fùµÉ, X†}à<Ô\Îd„©ç•ïõØ"%&Xê„Lumrvm5”æj«¼] ªc»lë*xý®9¸¢‡æj«¼˜· ôô¹êî§¹\.Oô“}%îY¯­æú©f`pü—Õnè†/óvýz ®­½ÕÜ,ã Øìý=¯ ó˜P¶ sî>©PF¾šKÔ,¶•Dm2 HüÒ°ïÈ. ð%¶Ë™ÆÀ_ïþª™³­`~Í‚[ɬMpC™MÅ4È.õ¹@Ð郠·Ô€ná]jœ:æ¿Í×k°ñãå[õéD »@Ï‹çƒq¬€Ì’¾\«AvN¾ùvQ­€¿wØ.ÐÓç¢@Ùz^Ð!ß.0楘×eÈ.€ç@Ëaé 5ˆ-CvA ƒÇv•>c¦¨¼`¾G`ñ颜eÈ.€Ï Œÿ(D¸ý0øÃçBvΨŽí2͹˜èx5ª æ)¬ßëJ­²°K4‰¿Çð¹ó¼]^ž7ÀiÎe_^N¶rÁcä¯äT¥: ;û±7Á¬êœ®[Ÿ‹Ø.C¡.¶à4çJµFmòÑWj›Ó= Ç'b»>—«KsxÄŽÑ¯à†žLÏ ÙAŒêØ._•B/;Là•W,¡Z8_Š!¶ àyt¥¬t˜º ½n4šÏEl„ˆ¡PÛ pšsiMK:.'oºô-5‰cäüÉ÷Tüšåì.äÇ'b»>—{-˜4çr’Ž Î©P·¡üc”(©úX¼® «@b» AN`ðK1Äv<¯öà&ïsÛ!b(os|"¶ às0„ç…ì‚ ±]`ðK1ÄvWüšæ<”½Gl„ ˆíYõŒ_a.ÅÛð¼—À!“æœ_³XI±K<-§›Ä_N…îõ ’Y¡‘}.b» ¸ñ%'…Dšs¯µI”äÔï5¹t'ðwįP°‡ÅR¶ æb—S¡¡Æ'b»>W@’ä`ü4çlé‘N‘£I{ðTžž² ‚u±ÝÐHs®mbÉÀ4IõK‘5©P—K1Äv<¯¨ò]šsù©¼–ôKzGŠzXNa#eˆí‚ÐÄÇy»!æ\0ΠÕKØì"CoÎb»>—«)!æ\¾Ë‘Q_r{MsNâïìéÂ…ç…ì‚ Ful74ÒœKä&—()³ZmӜ˩_~c¤+4Ô¥b»ž=}.b» ¸1HN¤9bã±]Ÿ €!ý`LÅÔ9“ó8Í“ù|] ªçí"Í9ù–æ\Eã%z˜ß$ÁÊi¶tý^¿2‰„î]Ô{^¤9'ÒœËo¼`%b5K丑ßlò–4]f#!»ü‹±]¤9÷SwIT®(ߘ&Íö_#!»>WÍù†4ç¾tWà+ñ7A”V² tCuliΡ¼A­¼]Üž×OV4LÒœk|0¦òBvà¢:¶‹4ç^ Ëï.™—®Dþ¯”t%ÒG$s.ZP\÷@vAðù\¤9÷Z­tšsE—Ùçä-—¹XÀÄk 2s¢‹5² À¿!ß.]ÏPæý ÙáòíÈ.áås€ì‚°±]ÙžÈ.QÛ]às«4çî+È.±Ý“<¯³Ä Ë]¬žiÎIvšs¯ÙÍ%êk§Ãe&)l¤ôBvÐ_r2Òœ+É®¢‚õ &—ÿ5ñ)ç!»èìs-¡×+_Áe±2ê øR>iΕ֣Uýªkh?ÒÙº¡.¶‹4çFn­×¯É˜Ùð¼ŠOi¤97HkU(o°¤ ƒì‚ ÇÇy»Hs®twþ0•2³È.bŸKHs.£°†‚.Ø9uzÍŒNª¥‡fœÿ@/ܱݼ¼77n„—çÕ÷‰&­²øR’…Ayõ]s!»@7ÔÅv{K<]*­PrK•ÈnN"Ù¾åì]u=r2šKtŽ×ÃÁÈ”YM4² ‚Éç’Ðs®rœ‹)2yK7.³±´ 23£+ªÇkFs~yE Ú˜òj¥¹] 'þž·ë{4À—ü‰\]yNJuA½|åÕPs!» (=¯¢H‚̨Ex#¬ ] ZÍÛõñvæØ]ŸkT»f­Ü¢ŠzT´™“-WÛ ë@˜· tCu¾]vžlöGâ½'˜-4õ¨óËžÜ ¾\é+ª‡Ór™Å ªÆq] ÿ(WgP*[zCYÀeûÖªR•]Q†³™øQÙ @n4ڨȷkäQç¾Óí{ µªYÙÛ¡|5€QìvÅžS@Ý5QÂWvµ3 €œØ.F1dú€|»² €>ý »¼K @v€ç² ÂÀó¢d€¿@† <u ¬ewüøñè`Œ?ê²²²Œyüø•Ýäƒ ¸ÈËËsûqœ!ÿ-û592 —œœà"[M8|ËÆ’]—˵wïÞŸþùèÑ£%%%f³¹^½zW^ye§N5j¥®Ú®3»¶Ük^±áç¾MºZLõê–6¸¶[ë‹Î‚KäØûí±óû‹¨ªªº²‘-ñLnÆÀ€¼õÖ[£FÚ±c‡–²{øðá… ®_¿þäÉ“•••žìô±±±íÚµ9räàÁƒ›6m*¿Â6Ò sÏ÷nܯs­NÉ-ã³ú·‰¢zDôáѯßÿ,‡ˆÆ ϲuè0,jÀ骪6ii=‡Và«£æ*–]»Ýþî»ï¾öÚk•åÇïå¼ùSZs¦V,Så “§é«eÙïïxrfÞ† ^xá…:˜ÍÞëlu-ukÞ°ÝÖßó¯ié<øÍÁ’¸ú ŽßÛv¼öŶw_l]P+¦î¹‹gw|óËû&‹©NJDávg`zpmÎçC³u½sþ¶e“Ük*ìö[îžGDMÉUòÃ6"j’ÚàXiY)™Zõ¸î¥¹£ &%&ÆSØbµb žš{èÐ!)Ù8y¶×ýõ××^{í‹Ï?rƒýÙÇM ’Mž÷…X¢)­9¥5gîîÊù zÁÛÆŒ96wîÿõéÓÇdò25Ø‘_5q@VSñ×#EUÉ7Ø2#¬¦»3"ÒTPëܼ%¯Q•»¤ÉbrV8ãÊâ6.™zÛcå§Î­Î][Ü©ªò§J?uâЬAåçNm[6é‰ï9z¢²cjêîc¹iSÇLs7Î&-)¨úÔµéŠZÍÌ­~ß³<3mm|ëj;¡IíäºÖsOnYa²šÝëËϲX­/^MDÿµÛ-Vëý†¸ŠËâM¥[·4MhèUp¯ëÙ7Êj½íöÛÞ|stttãuÊì5öîùóÌé[o¹Ýät­út%Æ4÷¶‹-"¢~øÁ½†£¹é¶£Ók&¢ó¦±Wñ íÚµkæÌ™‡ò7/˜å̼ÉÄ0R¯8e¦GZð’kÂcG§NºjÕªºuëz=€%ŸT$8cvýÞ<½Õoü¿š,¦ô”ÎÍê6mß°cjóæ³;¿?¾úìÉ‹¯Ñ‹¾†V-zÛpÎÊ_|sÇ;¿ô>úà?,©u£ûý¾»ouž½ Ô#¦1 RfÏ|mÚ‹ÑŠÿ{{Ìã“S+éDƒÒ—Ú׉¹aÞ„7dÞñÕj±víÚýÁ‡ùO¯Þ‰Iuª‰,fª!ŠŒ‰Hï”áJoӮݦo¿9⮥}€‘ €ÑxrþcMbZÑ¢E‹æÌ™Ã/àÖÜNwX8ìݹs&ò–{åkîáÇß~ûí?ŽlY8Û9°#­¹å͸š^xÂuï#Ûß{ï½)S¦p‚¼Ï¯˜ú⸅×$\MD¶ˆèµ§ÌKDׯåíüÙ]¬^T‹+G÷C#cbìǪN?³az‹öñû·'Zì.Ù:Ê‘_¥®û¶¾òáÐÇFŸ8_’Z?Áía™ø:g ?ÁÄ÷9vöÝ ŸY¬ÖWz ê{:l´iOý“çþ*wÑæÈ†‰Nó«¯~y$©hîáÍß™3fUçMˆéŸØ°¨Wÿì¹ë?×ÜÁ7ßzÿC“›¥µJ®_çÏ’Ê: Ñ5Dgþ*m\/ž"¢ø:õzôî5nÜ}ï¼óF9Æaæ¼Ç¾üàûnݺuèÐABs¯}¼ýÂaïòÿêØÕ0ÿ:~¡òòò 6|ýÕšÿͨÐ÷2Í={Þµó:xØEDW¶d®íH ë3låíӃrÕªU#FŒHIIù×ëÍÜà]Ë‹}§Ç™ê‘#¢`æ ÇjQäþ%DD÷´¾ó¶ä~ö‹ ˜ú§kìÎÒÈÊ/·o_¿=4p¿Îó‚"ÎGïλäQÈ9(ÿø»Ÿ/ª9AD+W®lÞ¼¹Wå•ÀË4ƒýû÷Ÿ=ýËÄq¦Ëe±…5Ÿ»½ïú³€\."¢êjÚ&Îpmúñ2ïÙ¼ sí5öýû÷ók.ûâ¢#¿Ê‘_5þØCeLAYtAYôÛKâœÎS•©…Ž~Ú´nÏêع·¼k›ýúõ­´WU:™ÿ8])º&gvcbÌW4¨aI¶Ä®.QÑ-×µX­]z÷ì·áì‹ÕZÓ°Ö²Î6vº‡ˆ†|õÊÙxÊÇÅ'Ü8çÅíÓ^úÛ,÷èâùS¡£â½w'Iï.sø¨ÞtêÌÙ˜˜ˆ¿J*¾ûá—¤äú™½znY³† þ:yrÌ­™\ø~ËÎ %ÕDtòÌé»ï¾ûŽ;î€f¥äææz•'_^ êQ·0yÓÝ»ëõjsý¢E‹üñ¹sç=z´yóæ™™™{÷î›%&·{äÈ‘º‰gÛ·åÆs×np•^ä>÷'ýßÛÎ^Ý#þu”‘Ô¡­+??_°òÖ7÷&¢ýkkÊÉ”9\Co©ºåBëJÓ±fUžMŒnøÆ#ïÏ›w5Ñ g…³vB"ÅF~;ýñ—æ=7þðG¿ßþcˆgÖ­­óЗˆ(º[´üÉd%ç.$4Hª°Ûó-_¹´Ân/`˜­ñݽ0®O72¿÷c|$“ÙQåøÎœAD}«ó"Í‘ŸnÙä¨vœ9ø{Ç=ˆèÞëŸ|ó‹ÿFš#½Î^¨q1-[¥¥§·«"ª›`)*)ž¿ðM"æ»=û“«Oû±vݺ›ˆ5×5mÚ´Ê~qéÒ¥‘01ÂbæÎf³¹W LŽz²Krªòð|ôÔ,X9¿œ’ü–ó›*V!§1J国•` ÚÚÞš³Ó?˜~âĉ©S§ÑË/¿²3²ÐÚ„ˆdjn…ÝiŽ,ŒtØk¯""{Aé×enëJDu®ïé.ygäïœÕ‘æÈ®ç6ö­Îs‹oçGïŽ4G6i×®°°ˆÞüâ¿»ô!¢¥­Þo\lìÙÓgöüzÐLTZŽÞ»y3ÅÄ0 ãt:©ÆIVë†eË4hm¦Š ²X,»víJLL„0…‰øò‹¿žSFŽëd—qó¼~k_F9ûå7Õk…bGí§Îô…ŒŒŒ… Ι3gÈ!ÏÛ«W/çuÛÞ•+W¦ÜÐÈ'Ù­ªªJLpñg.DEŠXÈÒË>º\®¨(r8Â? UÇkªŽ—_rQ™ÃUæp5®ÍDǦ’ó§ž_ûw”ãÀoë>8ÎßöŠ6 ‹¬n’woÖn£N$]YK~÷%¥¦ÑÇouœuà"ª×ºi£Ýg“)âæU¯¹×_,-)tTô­Î#¢7o¼cF#Û´—ˆ¾zóÿÜú÷»‹ˆN•—±ý{"9ÂK4àÌ©“ ë×/)*&¢„S”9²÷ÍCÈnïsUÛ~Wu KÌ7ß|;dÔè‹EÅgÏ]ŠµÐ»ï¾›˜˜XRRI “°€¢õªC±Z5LuIv19›ØþÁm–Ã[o½•––Ö¹sgiå%¢ìì쎶ÉþOA‹ÅBBšÙ)XÏÏSz‘¢££ù%»Î¼ñg·47wºªKL‡[ÄP¹Ãþò¬ç~ۤбêï¿:ƒþy>ͳmL“Z‡W¤6N¦ò†Éý1E0磦>;Õ3EWLySSSÓÓÓÏ×ýCN^d7--mÓ7‘¥‰—Lj;ud2or}´æ²•m¯¤)÷_µ(.¥¼}17 mË^yu“ýFZä&ÆJDö‹æ~,ö¢²7ߣhkt¥½2Ú½;÷À!C2u\µùýÇgN;nÌ˯¼x±¤¦eÛÚD”×4çƒÙÝ;1qµ\å5™˜V-\ÿ}”yp,SUEV+ÕK¢ÄÚ—inaÍ]ä²]{]‡ØuþúÛ¥v]ë?ÿ”éúeDÔÈe)sºÌÛ¶ÿxu÷6¦K– ²7nÜè‰éÓž˜>-??¿I‹T"JHJ,¹PDDMz¸¸¤ˆjhÊw&ý_TÅ¥r™‡:ûÆ;§}¹lè K©Š9A¿D$Ú÷Óˆá/}´|ºÛÕ.žòâº=Ûî)Ž™Ñå3¢+ˆNÒnyôã'ò>ÃQØI÷Tý…)0þñIDATmßúÝ 7ß=nÜÁ¿¶jÝÖTãHŒ«u±è‚ÓaoÙ¬Ééã'œŽ*ŠŽ\þÉ'ÐÜ0 ˆÝbâöº¹Ìz×ËÜ/ÛÊ?R9{áÏÇdœ¤N:ýû÷ôÑG;–:°ž¢m½Èn\\Ü­·Þ:výŠÅ`¬)"â2åM®GÉB»s¹\UUôÚ›Îj² 80))‰û[1¦ÿÄGÖvœ_CDk?oyêêî/¿8ÿÙ&óçÌ}i×®=ƒnê³éÛmDÔºuëé3&•V]\±léÔÉ/|ÿÃÆ®;Ѭÿ=GDgO^”¨Ó¾\Özü¢ÉãnÛúÔë%u¢Wf&% -Ú9¡uç’Òät³ùú‡n).‰úÔ”x”‰8¿ôA{A©µnüü G>úâm¿°Ï7¬ëÒ£oóÔF—Š Ó¯µ•]*<~èlLLLB´¹ìbáW_~Qb¯‚æWÙÎj‹XÎr÷\1÷»Ö]±æù/µ”]³ÙܦM›xlÎ+'&–¸å2åÄår9´h±sź¤3ï¿ñÆùe–L\¹sOVb½DTYö¥6>zø@\÷ÞN(v—yaÖlg…ÓT‡yiÖ|"2YLî˜CQåÙŠ ¦çþû’¢®›üìû‰èøÛ‹æÚ†R­„_œEDDõL%9#ÖDwˆ‹è@i05wÃS//þñáD4i@ |nU}úé§¼>'..îBiEãäÚËNœˆ°ÄCpïh®¹ì§Ñ W®\yÕÃi‹G}¤¢fïoˆ6lØ©S§ž~y~I‰}ìHSŒETy].—ÝN‹>p.xÇ:yÊ“b¶VUWF¶ŽºÏMDÓç3µc©nlåÎ=× ³ˆÜϧž¿pm¿fÓRú´Œ2¿´ýË¯Ž—¾´lÁô;'Ú+©y“f*4×M…ÝþRþçÏ\>Ù«Ân¿:îï¹Ss7TØínÍõ7Ï=÷7A¡¹ì÷ZÖêdi§VsIæK}êÖ­;aÂ„ØØØ‹åí=ýØL«–d6s•·¦Æ•˜^}Ãùõæ„ÿÍš7f̉:=Ù£ÌÑÍ£bË«˜ÓÅï¿5þñMßu÷6•öJ{Dáޟάýæó[Ž\y×è¯>xgúWf¯¼kâÕšKÿL?àÄj¥? ¾æ¶{¸åû£–kR¹IV!“)55uÒ¤IÏ>7ç\ñ™÷Öž<Óµå'Wa±«¦ÆU]ã*,vmÙæ|h†sÀpó¹â?ÿb“´ærœo¾½°ÝÐô–É••<<ñ–FµZ|öùz÷_tïwÆQüë-Ýûvoi~ÿp_4äÀÑܶ“5Ó\Rô ËøøøÌÌÌîÝ»ïØ±cãÆ3fÿòÇ\ºt©¦¦&"""99¹gÏžï8²wïÞ*Ú‘¿n£{áÖ4zp÷£ž‡Óªª+<CHÜš{åƒÍ?½\Ãj•½9Øl6§¤¤¤¤¤dffúïP?ülë‡øÂúá~Þ·W¯^÷ÝwŸæ•‹Ên^^^NNzœ ²›•••‘‘Á¾sÀÙü(»sçLD×@ d—ónauLœ<[“z@/ü§c—M kÝîfMÚŠ/ ìš ·+øÎv_мB0šë˜ úÄì?;h Øñ‡Ž1#GtAÏ@ÀøN<©«ÃIg IEND®B`‚jsamp-jsamp-1.3.7/src/site/resources/images/MessageTrackerHub-list.png000066400000000000000000000261701356450004300260020ustar00rootroot00000000000000‰PNG  IHDRÚ‹ïTĪ pHYsMœÞöœWtIMEÛ»Sß IDATxÚí{˜E½çëy3!‰¹€œ°"—„p`#óN]AA0* gs”Ex\9{–UWYñ ! 3˳ˆB H÷‹À‰— 3¬Ü#Š\r&dH˜É¼ûG“¦Ó]]]}­êîÏçÉ“§ßžêªzû­÷Ûß÷×Õ¿jÌ?ižÓ4…‹ž—GÕg-¸0§šŠ'WM;kÁ…ùU͇UÒâ¼+oº¯W>~sN-åW3@ñä¤iœYhú^/ê[Y"‡P0jÚ¢…çyvGYú P1-ŽpÇaå’õ›™`\C,¬GºŸ™PeZ¢zšŠÃ²º{ÈÌ °AC,×4fVXr €Àfšœ€ÄÌÞëèÈ2g-¸ð¬ÞÙ%ãy‡üØ·gÅý§HËøöKwÿ¬_ç¨È&4{•'«;{†ƒ®V®¸ÿWΤš§þ\›Hƒ–;Öqãú†\áUWÜJP(Ý=óù±Ô8K]³¢-´Âô-±Ž$Ç"ç¹Ã^I•þäW„ 6ò>Œl¢u6¤Äp°Â«nRµõíŒ,)Ù¹jº — €ºyÛÌŸY¬(@‘ KpO,|PG<" lb1uêtZ—ÆU¬–c©({_ú"ËÉ”Nÿiü¬3˜ õ“ãb~èÛ0KË=:H†%á-9Îïú£ˆ´«^!&D%õ×V<$íÆ(‚oøäUq¯¬kìq+þÄUJ&ÇÁç2’ ™{­˜®†ÍÆC‹ 1¶Ì¬H°_jœÕÛq{· „ †dz&…@r2 =“`À c+x'X!]M/«€Hea#ئEYÕãÞŽ0-¶mùU¨'õYRº1ÿ¤y‹žç+ô•ÿºˆA?øþYAáíÀÌØð à݉n–dü¨^áef€ ÇÈ1l&ÆCÒ­VË·g``ÀÙ900àp¶Óàk(n…îá9uÀ°KÅ1?us5´Õjé·âÓ\Ä*+Çjù s©é;ê6䪳×Dûöx Çê^°N€bÈ+vŒdU­/ò D¸ûcuÏ['Z ¶»ãl=o܆ PÉX±crKªÒøâb”××¢³E€ȱm’šm÷Pd0óŽKv ÜqlQsæ¨å¤q)ë—δ (‡KJqŸ-±¢E6¤_,V÷`(KoÊå¡_…5PJ9.LQ^¨'ÜÊ@Ž`3ò`Åàà §À¼wwwsjrbùýWw¬°ä9€ÍÄ{Hº¿¿¿tï°§§§ìo!å[€ªÉq___¹Þ^oo¯ï- UPn9v´¸Ôà-®ü¾ÁÁA.9eD+vŒ˜—c´ šéµXš&8¸6¨©Ü@‘ZìÄ—}aqéN3r¬¯Å^Î0p&:®é‹{{{]ñõÝ0ïŽãÊ¥T:ƒ’f¥ƒžÚ'ñ¦ìpÚë—½ª-•rg§´XPôƒ5D¶U¢èÇ@¤‚ë“]Ÿ|UÛ!Ã^ù„O¸peÑÝé Øé})= Æ 49Á$ Ͷ9ÞB½ÊX€oµj1=©ÈÆR^}y%j Pmš)w¦C'KdRÞ[Øw‘Ÿ(+|±7Ú ˜/!Ýé;Ü1àîNg#A[€kÙUÅì7õÄdEáœ|±ŽÃ –‰å‹SF‚cÈ&# Ç`^‹9È1 ÅÈ1 Ç`V­$`œŒ3ºI3ÄWæ{==Ýýýƒ™Œ·Ä㪀1ɰ‡¢åØ7æZ­–:Ó<Ò+²÷J/réMz‚±ç;„q …ÊqpÈJ‡`P²Ã¬tÐSû$>ﵚ HEÎÉúƆÔM‡I±å’`îØóQéšaŠCt~DâqÄ~·Ýñàœ9sì?AFÃÝn·ÛŒ˜Ìééé‰ë‘c¥x {9ÙšaŠ$Ý‚ÅnÀNw|饗>6øÝ;¯kO›*D[ˆ.!º„h±IÌýÏw/YÄᇠi ¢W}/uI,ÄF£í!“j!+–Á=i1¦ŸO Å1ä%Ç>ú袅çÞpeGW—h4+ŸnŸû±s¿1öû‡ÚB±Iì6«ãœ3VÝrË-6›bG‚½;]QfÜd…{7¸§§'eUÞ|=ÒÜ=ŠLU¾?µ<ÄÒeW‘#Ûâqm(HŽ—-[¶ø²MãÇ7ÆÐ:qè'Ǿ÷Ãö÷~ØžwÄØµËÚ¢Sˆ1qæ©¿ûL ¬Âãg+é‹Ó8ÐH…ÍuÝ/§¬(ÅÉñsyx¿–m!„XµF¼üê;ûÇÆÄ™çŽ‰ BÑÑÕX»feWlÿu  r¶8±ã4vXGÚ –?Ë­¼†Ø 6ëUç–‚ÿÚ›/cí±±·cIdÜnpç­\9n¼½Üq4S§n'ÆÞÙÞnº˜6åÝ?q˜#ïlwuM¥­Á`.” cÇȱ„=öÜێAž°•¸ãÚŽí·BˆC?ýQ‡xKˆ†xp°½ÇœƒùðřǎM­¹€`Åá‡üØ£:VÜÙn6Bˆyÿ žèÝ$&Mj Ñm±qC»÷œ)Ko:>«³ æ:Ñá0'®Ž,C\Ì;‹]¸«‚I§Uh†8Üz4ËÇš#áí[pTf#ÇÙ³ÓN;-8ûÿ~õ_OýÁ·Ú ÑnŒ/Æ !Ú !Äèh»çìÆû×þ]wÝ5ÝtôQ:­Â-Æ'm¿GÖWD.¡”r½%… j.¡ëOè/ä¬Bœ|òÉ»ísñAG5V­{·‘ñêêöGŽû™¥'œp‚¾æzõ4ì¥w{KÒ½Oß±ÆÙBì _w,„h6› ,8è ƒ¾{Å_~ážö¦¿ŽnÝ8²Íì¿?ùgËì¸ãŽöŸšà$9„ظ/@Ž2wîܹsPê„çMV±c‚ÙxdÎàŽ“Óߣ9šÏÙ¹¾˜x@3õ·Nþµ{õõgÏ¿íSÏöýùˆ]Ü¥óƒÛî:öºWo¸õÅ®Õ'ž|Öó¿Í©¯¶Ûå‹ãjq¬›ŠÃÝigqÓlfØEµiÒu2[£Žr,e¿ƒ»ößùà;v\?µ½¡£ctòs[¿§5ôÖ[ÍÎñ££“;nâÛd~‡Ä±cïúêãÓÜ”²%m5‹ä¸k|ãÙ—ŸyfxÂ^ûì|Óïnm4ã¼z´ý¶h Ñ;=÷±{ýháò nYòJ©Ï]«ÕB ´»Yê~·0“ù®ÎJ“·ùöx ‡‰²ïy Mïì&Ø >åáÝ#m:ìÙôŸ[zÉñŒÉ³ùËËCÃ;¼oÊ}ÍÎf[Œ·f·˜s`çPó¢nYºð#g}©g0®(˧‘õ²“yìØ§Å>­Öß0qW¸]¯úK÷Dn7|’Xµ sψƒurÜwFïþ;vŸ°ëé—µqxìõo|Àø­ÿS»=¼äw7=üÂŽ=ý´/~þÒŸÏ›.„xàÎ=:âÉ<ô®x««x«Õ–qöHÿ„/ŽTL¡ý¤\JÔŠV@ç™@E j>#u”ãž]Þh4Æ?ýþá¡uüjÓÞóÆ=ô§£¯ú÷«>ôÇûÐì‘SNûÄ /_uߊ'_7í¨6Îý‡é÷ÿÇ«š?ê½j%ý¥ï b˜ýtË—¶R¼¦×“”±ãœ/Ò-ýž„™èô∼"ÇIh4g~õø{ï_'Þ;ퟛBLýþO?¼¬o¶+·›2£c\ç½?¿ëôÞƒ66&-¹ý­£/ÚyÉ—ŸÜjüÄðî—WïËV«áPç¥wP"½U9Ť"ž¹(«µÞéC=¥ÜÚgóÒ+~ú0ä89k×ü²«ÑÞ¸U×Ê'ßÿw£s¶ßþíÑ‘½öœ5®³!D§O?ûÌÕ‹¶êÑÎ7öž}ó9¿Ïúûc‹œÕV[cQ¢yÇE¦[K#»H6r,„ç^|üö»nÁùklj‰§|eãâK×?úècWüdðÿýîO?õÔSOýUñ±c>|ÏÏo½÷î__pÎ{_|óKæ¾Z¹Ï±C‹Íúâ¸y2“.ͮ齛ç‹äFÞTo‡Õ (5•ãµ£—žÿ:ƦnX?kïíß4¶iÖ^;í³ÏÞ«^Y5øÐÀM·ßÜ)š¿ZþÀ×ÏþÖ¿xÑï?uÿmN5¨Å¾ 2BlбcÍ›f:ÅôÓl&¾S§îXÜí¸uB‰È2gÅ_»õ”ÓŽc#c+o^úÝ7ß~ýÏ+_jvtí±çì~x§h>øØg?sÊ/~ÛåáO½±’'42:Lp#Ì#ómä8Kn¿ú†WWíÝÞª}{ÌÚyæÌüãÊu¯¯ëlvu5Çï¼çö?ùèM7ܺð¢¿ì7÷ˆXçÜv {é5ß~oy_ >³ì=<™ã‹ãB¾c€\‚Bˆ¾túüC?ôÌñóÿ:cÊßO˜0nûÞwý—Þô•öÎþÀE—\¼÷ÌÝF'Íœ¹Íߥ±œjª³S³Xæ Å<<æT Çóo^-„xø¡§^^?÷ûîs؇?>iׯ¾g|ñ_.¿ük'»Ë.sêP›ýL.€|ÇyɱÌv_zã°wÏü²U²h¶E <29Žø"q!1úóŽ™-ȱúgf7gÒ\§‰¸°8Ɖsc°ÂJ3ï9“0ï  9>èàÃ.º´ï‰çþöÌËk× oZ¾â¡¥7ÞzüÑŸâ¼¾ÀG3¿ª÷ßÿ€3¿rö‘Ç7*ÄVM±©-öÝoßÎö¾¯üíåÏœô¹k®ý g_gQÁv»øX‡d5ä óŽòuÇGuìé_þ—™»íþÚúãšb“†BtŠ}ø#‡vؾðÏú‚âÕßKCÔûu*ŒÛPN¢«-§|ÛC‘½Å#ØâŽõ_1iêÔñ:û®¾q—Ýv¿dáÂ'}ì¢ÿ}ᔉþËA­ÝçÌ<òÈ2h6]Ÿ˜Rž¼~3‡ÍÜ){ߣ¯·Ò?™¢DùŽJéŽûôü^úÛ„ «ÞØðÞéÛwÈÁ,[¶êùç?ìq¯®^³æQ!Äó/½xâ‰'fh³r¾ ÁzûƒúYUaNÙZðÅùÊñ¦vcß}÷bÛ©[ýà’K„hÜ÷ðcbãFÑÙyÓ-·LšÔÜ$ÄŒ3V¯^]Œ ‹Ÿ}¢ë=ƒ†Uè«Öç»#«Ê#|¡®Ü‰ZØ3™w £¯{mí<Ø¢!Ä{§Níöa{ï%Æ¿ç׿éêè\»úÍN!/^ÜÑ£õ ÍŒIþäOšHI†UUÀ&ã‘ò’ã©“'¿±fõò_®hñùÏÎ]ãEg§ívïûéã{ßvï¹ûî_îôþ¦OŸK¿\§ 9){ñ0ï _9¾qéu¯¾ôâÔIÿضӦöýøÊÿØÞ˜{ým·n~kí«ëÇw6GFF®¹æƒúeÕ­Äo¤ìo_ à’×ÌŠ«¯[òž‰“þ¶jíœVK¼½áÄ£ž:yÊ‹Ïýeú6Ûþòî»V¯yùºo<áSŸä{¦Ø‰‡°í:ļc€ݱbÅòû~vý’Y»ì´qÝPǦ‘­'Ož5sçu¯¯}ëºo\±ü¾¼}®k–Nù’âV˜`ÖšôØdUUÞ㑊“c!ÄíwÞòýK>´âWS&Œ{û͵k_yqtxhýºµ}W^K‹}êöÒ»¿½%jG)­PÑhض¸#«ÊÖ rp:‡mÖ8eì¸ÕjsÒKw¦!ó  V¸9¸óì¯~™óžŸÝzâ óÅ)ïãµZ-79½Žn:e2ÌgŸy…€CE„8«c‹!ïØ±+ÐŽ\º/]÷*¸WR¥Ê,¬_!€±`@,œìXŸ€úäÏë[}Ö»áàÓV÷O¾æ|…5+ÀƒÕèç¬ðéZPv-÷¡èr­H6‘c0ï‹5clj7R ci¥~x:“ÎCéH|é5 Çg|ü‚öŸŸ}cìÅçÆÖïÜ}ôÐk–.;w«‰ùëIúØñÀÀ€3ù!LòÔRèÆbÝTŽU!€9þÄçì¸vÕÅ¿øöewüÛ»Ò<û_Cœ6kpñK·VàlêLcˆ5 ¹˜¤åòÈ ªä~+oÃðð§çœüõ9_XzÏÿ¼üÉ«S¶¹¯Ùýè?žyâÄ9†‡/[ùàÖé;¯Ñ;éDZ}­TgãL)ır½'îyÅÈ*gEðž›×·6Ü)d$¼;ƒ·ò…vÇŸxÿÇÿé«g\÷Äâu×^³l§COZ½b«‰¼¶9w›qïÙjâÄáÕCí¹Ýß¹ãü¶³õ›‰s½ã”Súb…gX&« Š“ã ÃÃNx\sܺ¡7&O™:2:âD$.[ùàôÏþû¸æ8!ÄÈèÈçÄãbD\±ù¥¾ä)ž+S$5VK›»mJïj.Êä¬È>Xáj±â‚Y !¾>ïðk›s¯øÓCãšãÞÛѼªû#ë†Þp !þ¼nÈñ¬ o0×»/¼ŽÃM°zH&¹Þë¾ g€È#vüÅ/ž-„øÎKBˆ‹îûœx¼kÚd!Äš±ÑÏÞ;yÊÔŸLëBtM›¼Ã„ ®ˆ§‘< Ã)r…í„|Ç9Êñë·ü‰?üDìu—Øç.±Ï7·Ý}dtä°ÑÁ‘Ñ‘ÿµß¹'­^12:²~õZýH…܈«ªgLC_ €K8}û£§½öNüá“C¿9`hùCËÏ^¹âÅ•gLØOñµß|;a”¦F“ZÅò*T¬LoUʱÉZyϬx¾1"ÄøeS>xÌ ·zè¹ÓÚo !fî<ç¼»¾{ñšûˈ+£és½×ÍK=2ñ @޳äöï¸òúßžrï#O7&_÷ÄâÞzkÛvÛ‰'{î.(gaó+|S}]Oí¶3ŽQs!ÖÏYP0qÃhéÇpöóŽO=aÿSOØßÙvnÖe®VÞüîÉ ëlÇ5ÈB6ÁN]a͵_ –Ó××§?’ÓÏ×$…P–&W?×;BìõÌ;›ƒ¦•Ö×î0Èwœ±(GÞr¬í-»HÌøšƒƒI˜w àB°ÌûâZ¬X$IxRFs]ºÛae‚é& $nQ„,E¦ Ü1€IÌ;ö®œ¤³$’omSu™°lÁ‰[”&~ƒ²ßÊâÈ1Øâ‘Ód5^×™,E}\ëªÓ¢¯<Ö¸,Zwø!ÇP’ÅŽ¥á‚\mKF _\^-ÎéDŽ¡|¾XªÈŇ_S¶è ¶@¹´8EæV&ñ¼ã\£+Xܹ•g?|:¸c(ŸGö.¶ä[³#qh/X¡æ÷3A‹Ò%£pÇ`’d9+ôoÇ…ÝRS—Ô?0q‹,æ¸c(·/Àä…ñœ8S@޶ðÈ?'MÒ"ˆ¢»·Wÿ§[K( Ç`t¼ÍwÌ*$ ¾`ëËLbnÈ1ÔÎØù+Š[y`ÖÊ@ŽÁ.Ìy;ñÚ…°mäª3ЉW€ÍvÁ Y„m#Ç€/΀¼Ÿ‹ã¹»jø×:ø¶‘c¨fcÇŽVz3øì²[&øL³´°wO°~¨˜GFŽœ Î3 ¾Tñ>‘ î÷î +æ«JjÕ9†J tËcÇšzŠìÖÊ;|±Õ§­ª¾Ø1TŸ*Í;†/ ò9ýÍ$3FbM¯áÅ €yÇÜÊ[¾˜Ø1TÐ'þ¡­ n‡•IfßfØŸ”U¹Švñ¨CBQbÇP ÄŽ¥¿Ð…¡€f˜å,oh•;„±<2r u÷ÈAÕ3Á † Ê®ÈdÐZcµGFŽ¡R=Á¼ã\õBj´“…G,4¿±®Þx±ôÀÊϲ v øâA‘Þãò™å"–Â89õU½í*ú¦¯\êJÂz÷'‚ô@o?½ïZz‹¯&‘bbÇLtÃ$ÈY¡;.ì&^dÚø°—êbú}KVâ-è—ÕÅ˺E3ÈYxdóÖXí‘‘c¨Ô@7»VžUFÛhvúbõ6Á ¨ˆ/6¢Å$;†X¾|ÇP}Lå;¤<†8lbÇP#Ìyk­±×#·‘c¨Ô@'ß1Øl˜w øb[|óŽ¡úŒ‹§EâNlË­“ò¹mýìY {ftdõ63|SÄŽ‘c°Å#‰WoZ¢nÒ¤ ™÷9øÐ%Z¬Ù™b.!ä;†ºüt=²…Ý“>Ö¬ó¬³û„±â(·Œ49†O#ŸEÖ©?ø<·y¬\¿·ê³ë]G*¬ºÿ :ãÝ©x¼žyÇ€/¶È£y3¸ë˜Sw™µÕÍÊÜÅmËÍ $í@d Ò³‘ W±Þ~dÿÕ'VçÓTû†æãŽÁ0u^+/™»n.òðlSLdHVO¬7›“u`­<À#C´îèdVË<ûZÁ åéDlòh·°Ø1r &q4Zœ·åÔÿaž Ýn©Eö?§7(ÂçTd>¿‚`˜÷Åœ‡0ךº½÷bˆl‘yÇP ÌÎ;ÖÔïl0étàUexˆOÂÂr%^z7O¿iu±dç*ìíDö_} Sš¾¿:Û==}:9† zd³ñŠÈôíŠ:ÅôÕ'è’fýê õÓóëOUÖ_ “#AsŠýýƒ®fÞ1TrV€A¼“Ž×rV@|1çLý$Ò\6»˜Ø1r †©ó¼c(—u`Þ1ÔÅ#¯`AÐyÇP¯^¼cÉAójͼc¨‘/æ<€ý¾Ø1TbÇPëwì9D+}JXñlXiþß`ù¸M««•nûžM(2w}ùŽ¡ú$ˆ»Âäõ­Þ—Á¤©Ç¥et–ù¶ÙtÜ€C­Ò•Ú##ÇPAœÆ GF¤Ûae² tdu 1ƒÖØë‘ƒÛÈ1Tj ÇŠW„¥µå®é‹ÉYøb¹"Z…\}óŽ¡ú$žwœ«òæd´ÃÖ6Ø%Hé‘‘c¨µGö®Äî˽«Xž]SìÔ*ï¶â+¯y¸´*½ýÖX푳‚‰n`x 'ÈY‘,»~žruáéçÃòµë¤~×Ïõ¹úbrV¾À ß@ìªñœL }Ü&XôÈ?'M²cзÆä;† t#9+È$:lbÇP#_Ìyû}±c¨>ä;†²XæÀ°5V{ä¬ v †ºÁرC«Õ=00XL;=­V·"¸Ç!lð¯¾&ͪŒf?Ý?Å-ùé$þÔÜõk(,vŒƒy_laîyïw5'½vªUè‘·Ý ¬'Öý2a­(ú¦àqË@¬vÉw µÀ’Øq«Õíª³üßûÒ[^ª‘Ö8± éÆ01Õ)Ö7…:óÃ%xÚûÃv »÷3 ŸGn¬“=‘m)BÀ™ì»ÅZ­VØ9 +ã«Y§¹0‰{ƒÁwÄ•€`XD²Ø±ûevpµÃ}éSO¯H¥J±ßלb¿Wé‚ÝS¿E÷¤‚«–TÅ9Q›â°wyŠôß Ç`µ/NóÃ\¯„é‚A½Ðéž }NÜ\ð@Í r P( rVHY‹Jß)Jü¾¸{†ä둃Š\íp¤~èC|`Œ!Ǥ™wœ«ÊHMez›Y%£šæáÙ‘c¨‚/v¿ÌÒ{S>³œ@\¤N!»åîë‰ïOÉîhkÓ,vN|etÞ‘~'qâi`¢&Á¼cýÛqawÉÔ%/ÕÅtê×éRd+ú…¥=ŒÕVXyS¤yw åöȸc€Ì°a­<œ Ç&s¹ñX6 Ç[`0g)Áª 6±c°Å#s æ Ç`ò Ç€/° bÇ`ãùŽ[­îÌ—‘«Ù·§Õê[®9ïìqÛü«¯ µŽ}ú2šýtÿ·|ä§“øSËïãFŽ¡:Ùªx…÷K›ÓØ©V¡GÞvƒ²®î¶¢ÏúeÂZQô3LÁã–'XP4öÄŽ[­nW}œàÿÞ—ÞòR}Œ´Æ‰eHÇ0†‰©N™°¾)Ô¹˜.ÁӮضSQ¡úž¸Ð¯½³á蔳ǻ­0Î…Y<ƒ¿µ5ÛÕ‰„$(ï;íêCç,Yµ@ŽÁ06¬•ëkYÌwØæ§Žå׌ù&‹©)+ÒzdÎC¬p„Íý S̸åó¾KCÈ1Ô”ªÎ;Ž WÏ/Ÿ B70…”ÒçúíÕ™¡ó>¬°N™L´RpO/¾y ¨)&v †±!v&[Ò»y^EK3ÎwC?rŠ®ŽŠ¹}SO,‹,#í[Xiç¥3ˆc•W\HÛŠaý eÊ2#Ç`‹G6¯›|¦˜”ü†« T¬#çç&+ŸÉžÈ¶!àLöGž±X3Ã"Š£‹ÿÙF°LBÎ ä,òÅ%ê°NDBÓCÊól¶Bä*ˆ;æ<;[K€lá‘‹l1®C,©£”^Bo+$$ŽÛ`÷rêNw P_lC 4§>ÞEŽŒ‘ù¼ãüÜe«Õ*µu-{ÿ‘c랯݂å q[qt-­"G)"?È1TÖÛÙ7G" þtÕ¸½5ò¾Ü¥§"[7{òc]•‘c¨ÆcÇÁ¨ûUÔÔŽl%Àkcu ÈަݵJÐLìlñÈãÒE¦³ ƒØàôÓ÷$rE׊ÑÝÝ]ä¯7ä wƒ9+ÂÅgʼ^U1CËë©#÷눦âÀ`µ‘­h:Mi‡ÝÁ3,9‹CçT„õ!âWWöQ꜇þþþîîî¾¾>ò½½½È1à‹³ôÅúû#sÏ+ökÞÈŠu`âûŠšoD'µúÖÕ™òÕç-ÖG©Ÿž¿ÈDBÄŽÁ0䬨Òoÿʇ/c¨…G®ÞûŠ+L岸Õr VcUÎ ³;󾨔³  ÇïâÄŽ‹WFÖëj6/%Ãø Á °Å#sÀf ˆ•#Ç`bÇP^EÎü¾%r õòÅfÓPèôÊ΂O‚ó˜CBì “wìØ}œ¬t?„¡nr ¶xdýx…ô±W¯ìŸè•&ö}µÔÕ†ÕVØý“¯¡ÈΗëB+ "$‹»yETºŸ°Ÿ–ÞÔã>í“V«VsEá`Cúä h_lÿïÍXå³Ê­îª6Ô‚`˜d±cK|e²n`б È1X=èãÆŽmÈMžÀ½ÚÓyÐ1 š 63¡1ÿ¤y‹ž'„XùøÍBˆE}+…?]ü[Å7Ä ‡ñ\(V„ç÷rípÐGŽº˜n…!šïç\a-Nó›I=ìûûû‡×=^Ü1”É;è–ÞÞ~0Ééƒ,‘!ëîîÖï­óÖŠp9+~Æú±Ê“*(W™+E?ûûûûúú4G‚S¸øN2³lñÈE Ûé·9†jz«B Îïk¶³ÚäðÅ¡DÎUðÙN¼]™ágÛää “ÕZyš_-¼-¾ØZEFŽ¡ YçK…·ÍpEFŽ¡jd;Æã‘«¡ÈÈ1”ÛÇU¼­ vl+È1&}ì8Ö“Êx[|qÊ!„9í× o+ˆ[¬ÅÈ1&ÃyÇ‘_*¼-Ùf-FŽ¡ô¾8ÖW‹ê²ÝF‹‘c¨YÍ;(;ï¤:kÁ…œ 0ë‘õãqxGO‹Ãô׫Ô>-¶È6Û¦|±»³¿¿xÝ#AáŃ¥¾Ø DôõõÿIo‰ôôt‡i1mË}1îÌ»c™ÉeÔAŇ=îÀj˜w ÖÁcÓPI"xlFºkN"Ø6jjçŽ{zzx> †Qµ…Ø1€Å·—S`^އ×=©0,ÇÎl¸ôœµà¬ª0NÞšÖ‘Ÿóá@•´¸hwì<"’-yÔ `Šœ4™VÐÌÛ‡µ€*‘Ÿ¦5æŸ4ó `œÿ•€pKñFÚIEND®B`‚jsamp-jsamp-1.3.7/src/site/resources/images/MessageTrackerHub-table.png000066400000000000000000000312431356450004300261130ustar00rootroot00000000000000‰PNG  IHDRùáâQPE pHYs ‰ ‰7ÉË­tIMEÛ 9‹Ëpú IDATxÚí{´Eïkß1Qö$£ˆ‚x1AF=ÇÌ ‰<.%:,%ááÅD¸Ùú<&BGã„QÞ9G"râ †Y¼¼À À Ù÷Û¦ºººº»ºwu÷ç³X¬NŸêêª_Wû׿®ý«Ú¬™‡ (5]BˆEלg·Ò9óX¯ ôd$žsæ-¨gQ) ÀñlWÛåý{p`…õsdQ'@é±.žul Pzº¤/j:þ>Pn¬ˆç¢kÎó¿Ô]n+BŸLè#üú°rqÛÊ<(œÈv\ú¬Ô ÜÏ< Âåx ]šÃÒf¤Ïºxvq]!.3>}£·½úþ¯Íøô«ïÿZX1ÍŸ¼L ø÷ø‰<Ü_&l¿aO¹ôP\˜s …~õý_k ½FˆÃþd®›^Iÿ!íSÖo²yv€¢ƒ_ …Þï×J|X’¤ëÒ ÊPV¦N?^_`μsz¦¢õ9arXÁJì­‡„¾&bîÔkkoÇ­!ý›Jð¡|>i¾@8N´ÖG¾AÄz€Ò¸ðzákï׫vJM”Ò¿”hšäï8rq‘„4Á4òë™: ajTü°ð½^c¹ó±¦ÇØzEðOBî!Rb8Jõ”R.mÇrí­OI_ðûæÅ ` õ`MñÓ»öX #˜_1ʱÒknÏ÷þ3÷» Œ€Ë ¬(îWI#¿Þb¢c(Üë9øW&_t\H£µ¾Ð2ÈZîýßfaòœdë­Åüyà¸ÃغܖÅöA¡÷ökþ©Ì¬ L{ Mñ4å°ÂÊmI¸•Íh?˜_Å…o³[è•b§ŸhËSŽU¬“îW6‰‡Na%øƒÖ¸‹­à1€òƒÖ”Ÿ1åÊUéƒDLÖ€Âá‚ô¥¯ARõ®šgÎ[P{w¡Z­6gîüZŠÓœ9oâ'?>ÎÜù È”–½ªÊºðlíÔ™‡.úñùï*4wþ·ÏYÄè("?ºj΢kΓ¤_Ž×㆔‰¶ÿ·9—ƒ?Ç(¥ÁŸE™y8å­@ë øÄÈ‘ÐÝÝ-íéëëkïìëëó ´·Ó (n…Þá5 ÌZ¯TÞì¤Óèîînó³H‚޲ˆô¹Ï4β߿NßPïDžôûÝi¿p¬æë(YÅëƒÁ[ÕJe|ÆÛ«yþ:z¨´_o×[{¢$8VÈ œZKÓxôùȺtÆö䪮õ®éµÝæ!÷PJ˜__°ç€C~}ÛAÎ.¸Ÿ²~åáL´^-š§‰å2òDæÅb5}€ªk½ò+kâ˜Û‰ÐúŽ©-²Ð1­€!-CÈ<€’ }¨_ßßß± Jp½YµÖ7 ŒPD\yCÐÇ'^PNüëÍvµ„hµZBˆVK!Z˜ tÄóë›ÍfázØÓÓSô.¤ì2@ ­o6›…IÙ›ÍæâÅ‹KQù´ µ¾-ô…‘¶Ð—^‹øH€¬1š_Д\ëz€¢Ó•^è{{{½m/ÞÞÙþ§ÛA¡W®?®Y” TZo.ô~‰jzb•·ò0ôèý QY\  ~}\-Vêrðyö|žÊwˆP.Š¢\Û$¸3l ”àj·qÏ`H޹ϔj.iºôl>ÚXl•¤ªšxާ¹ÞÎ6šþ*Ov.wµ^ƒ¡|KOˆ,œ÷”‡d"²Óúdz­ŒääFdÈ%lgûŸ±”Úä\®k}â8Lvrï…M‚$ÁŠ¡Ü'˜Òcx.€<´>vÏÁãv*Éá2èæ.|Üs˜6§q{òLpj•òþÂÞ,ì_ãÑûƒ0šÙ5ÊÒáíWog{#Á¹òÓú0ÙÕLÄÔOÀ×ÎÈ£7ñ̓ebyô) £ïÐy­‡dà­Z_~ÐwÈ:&¨¨_ß^[¼gñbQ…ÅX Œ°É¡12.ÄÐzQ¥YÊñB¯÷óˆ×“eG£çáÿ-+½½½===UûSÍ^cÏbÙ3Ö!ÍfÓÄþFs. —›j^_F5ö¬ÄpÊOiK¥\r$˜^ƃ_6%¶³-3j’ŒVÙ,¥´³FFë /÷a©Ê¨OFè;KY!(Miç*Xb8¡n‚ð­1ÒýW‚%¡¼Ò’’eüf4±¡y %0NXÃ, ·%¶X¬ûWcC©@ÐV徯Ëì×·óûÿoåý ¸ªIe϶‘æRš.ìÅ+²†B¿_êÇRd¯%›ûGxi,–lìEÚA9\«ãøã×Ë£'xÕywŽ4šÉsToF#ëBxîa›]î¡«{ܶòëóùTVî5Nzem6À°kšbÒ k•G£~éÐ6ñ¾ò=*ºJ?b9VÊ»¨šNSgÝÉÒØVjÈŒƠÐ'{F.=]Pˆáà¿cä<äžKÌØÃ¯wËÇ ûæ#Õ\'¶â7IËH†Un—öޠRv-8À"- Ò ŽFÍØSÚ0øyÖ® NÎ!†S`‰O³Žk¬c«C²%sãnÑÚšöŽ¥È=É–D®ÔØ‹4uð¢”ØhÄpþ"•óëûûû+•Ⱦ§§Çß_éŸå£j׷ʽƞh½žFOiËz‹6žª]ßjö{ÈžýýýÖ“É3¿`?ùÏS—ÌYÞsÔéÞå£Å; —]ûÒÛO|èÌ;Ö,{ø-ØGO÷ëµ¾q-ì`÷mÉ0aRŒx=€BüÓE{ìýÀÞŸ|þȱ[[¶oÛúâ¤)c„˽uûÖís~ôiLd"÷ݯװCÎFϬùõÝݵ¾¾–ùþ4%³«!Ó‚g1!„kF»ö²g/þÎè½wÛ¶ø¦P}T]qâÇî?yÚü{¯gqé"hûõm¹×ûøí1𷹃²§Ëüþ,÷õ@²s¶³S¿èKŸ:á“§<ýÄôgŸ<ø}ìõÞ÷ï¶ï>†FÞ³î¡ß~øÁÍ÷?|÷Ö¹ú7ou| ¹?P=Å“{î5wµ¾ýö_!ï±,]³°ý&,i¾S3ªÚ{¤£òï`¥\x¿=Ë´¯‚#†ºìö•?zÔ‰Çÿ˺¡é_XsÙE÷í»ßÇûÄ`ßúºbãÆ?½k§EWm?tÖˆGoy;墚Âúê²â›8øÊ[5Ò€‘{Ì‹ênJ•¨ÇHë¥Û2î¶É_#Kšì4lypOάš _ž1i¬;|Ô››÷ZòËëž{~hM=b«bÁߺäû—¿ñöä9缕Ýb2¢œz@&é˜÷Bo“ÄÆ,®Ð‹?Jòm6Ìd™š2²r‹gïH«€ßŸU>›;È®?0vxmÚž#×<óêÜÓ/º÷®ÿh ½â‚ ¾7vü¸ú[-“Ž•Á\¦ñÖ7®åùøÒHpm ¸/ôÊÚôë5ïæ9•òpωˆõ`ïH+%÷~Ýï,ÿóÒÃ~ýÇÇžØsÚž#§ì¾ÿ·fm˜2qßÏsÔg>{ì¯î½ó‚ çÞ|ómë×=õÔÐ> ‚X§B"9?Û­äfa{ *ô‰½û®d–ÕGN²ðSŸ´(¬”Ü;Õžo_ºêÆNüÓ«Ï}ûÜ]{æ®BŒ>æ´Y§ïwàÎÿ¶ü•I'Ï=÷¬CùÑ/VlÉý&ÏoÌç©òùŒ(å'¢"sl¥l©Ûš}úÕJpŠ*½–ºrËýsó#»ï¹ý–i}cN×qÇž¼ò¾ûê£êËo_~íU7Lš8yËЛßÿîÅ/ox’WèÛÑŒã¦~½?"½}+÷océ(Í3VYò¤RI,Ø_­ÀÑÔo½ƒ„k¤Ëí }¾Û³Fôˆ[þmÔ]K6þêÞ;qçòµkÖN:õþS¾ôée· !_<È÷•ß>+jFþÝý÷ìúv×°Ý&ì}Ø­w]Še$jµš¢Õ"jï(]¯´ò2‡íOS2»2í ¸/ 3¹ôŒí›>±äÒú˜®o7¾\kmø ¨Ý6øhû¯g qá¨>³©?Á€‰5N¼Â…]._Ü"ZµÑhxÞ}ض!õX—°ôùÜo­VË)ƒo}óÍ­o¾)„¸õ®Ko|àw_ÿ‡y£ÆŒ¹è·Ë>¹þ­—ž~kðÂ^=õÐÙ3þAñÞ½FNØû€ÜVP7Âñ»©@Víïï÷4=lÛ¦_ß¾rþ‡¡w-%«…í7ùkdIógª½G:*ÿVÊ…÷ÛÙ³Lû*8b¨G^{ÔÑ3†¶ ï¾yÓk£ÆŒÞ5|hÛÐib@l—|÷áó¯¸Xq}×ð¿;ä°Gú¡á#Mó.h>¼ý¦+Pœ$}S•·j˜­Âì“̪þgUÇ­Ýh4^ Çä (SÓDVnñìé`î(ך÷ÈŸ× !®ìûõâÉ7~N<.„»Ó”›'íp Fc®PAW#åH.îðK|­cuY_¸ Võœ÷¶GÜÎVë½ÇuÜK˜ì¨”‡{÷[Ücùw°:rïÚDZù£>4Ô‡Þ7{éÍbúÍ“ó÷m mÚ6ô_xþ´CæÍ|ùÑö?Ûn¾-o·:ãÄúµ®Ž-ÆëëÉ ì§åÃå€@G:X)¹wÇ> oÛ“Ùuký¹y6=r³÷=øè²cÿûù_¾ê½»¿î®Kø°~1’$©@¡-g¯u¥n´0>“x}ÖojE9;»õjÇ›qæ—Büj¯Øg§Ãç.Ü󜟵÷ŸrçOæßvŽaÐF£DÉÜTÞº)TåÎ…È5^/}Wº-þýÁËìÿ«>h«¬GyÒ°3JÕJ3"4õ[ï áérk ØAîZ»äÖu¿Z}ï¢ï?»â×äàË.¿|ÖÌÓn¹õædãD?BÌÇU²‘–çø¼ä†Ç<¿½mÒª°m× *Z_z#2Jr¶³›ÿÁ?úß·ÞÞ5nÂí?ÿe«&öxÿn/üéù¯žttÏ™sç/¸òüóÎíÔ˜LŸ>,7ƒ{Н—û,(Ó-lýs7ùëÉ_Ÿ¹ /œÏ_ßæ¤/Ÿzöyî±×n÷<ôØ?:KÔêbä(ñÖ[;ítç1‡ÿݘÑ#N9å”eË–ÙÒnóalx(³±ûwêG¸Ýù—Üð˜ÆÁ÷®~ðÿ†ÍÓtV$1½–ß] çãi}ð2ÄÚŽû¦¦,i²Ó°åÁ=9w°j.|Qx§U;øàéo ñÊk¯ Qûõïž8ò Űa6lxGˆ½öÚëå—_ÎÁ>%ÅdXJÆO0Âó é$‹ðè߃µòž”'†ro8ù’üõ®t° 8ž¿~ó+|ð7]BŒ;N´ZG8] ù?2vä¨aB,]º´^7º_jï&ÏÈCʪ²á—Üð˜çãÛ½“%¬¯, ¿Í&»QSÞÞÉW¾!ºÙÁJɽƒë+M?þµ¿¼¼êGŶíbÄHñÎ;B´ŽjÜ7ðø=÷<°Çn»ï¼óÎ ¤ªâ£"_Ñ\ÑúįB¶¾°g=:ÒÁJɽ› ûé·]1ÿêW^}Ïý¦ÝvÇò7ÞØòþßûçþôô“|ñ¹g¶nÝtË-·täÑXÐq•Lå‹l)³_y…ò'Ÿüõ¨y¹ù?Î?ûØ£3nü°º=f܆çŸÞiò”çÖ¯¿ié’GWý:£±ZÊq9ÇÿËù‘VÍ燩Ö/Lp¤†-ú.½¼û DæûVæ—ªƒ«‡9Gšú­wpt¹Ý·Ïw¯øÊ¬¯î÷Á½ßx}㫯¾:qd×Ï~v»¡‹_EŽ+Û&¾…­_ˆÄî|Ø­dë1àrüÐ-¿>2ÍtäH5ÿkØø6l•Iþú|:ˆÜ"z#±ô–õÿó Ÿ?6q—#‡¨É¸24fä½`~3æ£òšöÄê~¬ÄôÄpR‘ÿ§‡n*(úh=”_èyí&}Õ!=`O´^ò×—ý%=`Ï*<8Éi :Ö¯_¿ß~ûÕëõZ­6~üø… bPR{€‰ðNƒÖC(gœqÆ‚ï}pÝCOmßPo½8lómzeî~hûöíåî8ùëË=ŠÖÇ1ÆS_+8I6·.yä‘Ç|â¦ÅWÕýg<{ö°®^wÄG$kª¾ñŽ\Í3>~ý7½o·]wÙeÊðÑüüõ‰/P²^Ç­Ç«¶oµo™(~šÞIS~nÚ­Gm®máÂ…gœtÿ«Õjµ6´®X´}Å/wÌaèþH½{ÚÏ?ÿ|)/®?ýM?ý僿ùýÒ¿{ïý÷nç¯ïàH³’¿>gÅ·8V•Ó%½T ~Úb@WsÇJ­É¦­¿Ba™©EüÜßš,êúV…uG“CUÓweK„AÞ|ÃdúæYø#YºäêG~±Ãcšú÷Û7mB´vÙY¼ðÄ0!ľS_xÛ²sÎ9'ŠYÌän ò×[Ì_ïÉ}ëðФ•š5¹4[Ù£Ä7~½z˜zOQiOXö‚`&ÍÊÓE>ü#k P¶Ç¤;"üWŽ&}W¶Dé§èJS›!OÖ;dÓæ;ÿüâŽ#j«V­ÊÈ“MÜæô´ó×oóå¯o½•[þú`¯•m~÷½{ó›7çNä +B~7ærÞDh}¼wCÍ~óT Êäó”ŽUÊ_cÇÊÑŸiBÿÉ#2ƒ»ŸÉ=ä¯ÏÂþšNG”¿>Ú@Ák>š;K°aoµYw'ò±Ú`·6?¿ûÃo{æ ;6¦í¿cã•ׄáçÙÖ»q|œøò׉#ŰaíüõÓ>´ÜüõêuæwßµÚ5}$'绬šÄÎ}VĽÌv¶%±LY8ÍšvмM›¯œ0¾&„Xr]ý¢³EW—ØoŸ/Ô_8mû¿ÿŸ™å»È_o]åEx¼ÏÝ]­·øÔM¹h‰ÅÚ vóäõLýÎw¾ó‘ÿäÉÕ[ººjõzÍóè[­Ö›ÛOÿÆõïyÏ{²èQǯ&ùë- ½¡ÊçÉ!^oúZ$%|O0¿Õ ¹ãéÿà«6à~ewôI¼hªòÁü’I•ÔNy„&LXùàÚ>µëÚÁ¿ýlêwZÿ8oû¤Üøõ¯ÝŠ»jx5sæÎ»W¬}ü·Fûõ7üiÛ››ìæ¯ÛkMþúX† Öí¬ÚDzâʶ™?4wYgÇUaüzsti£“˜8÷w⥊Í[’`;}dµ²Ç{<1ðôÏþóo^põOë M‚u R&©'‹=9!|ptutÒI't¦£çÜáÊ‹ñ6øl€Ðƒƒ~=™©Ë ùë{¢õB¿¾ì·(ùë{:øàl4vŸ ÄëAÇúõë9æ˜õë×·Z­qãÆýà?8óÌ3ËÚÙü_§.™³¼ç¨Ó?¼ËG7Šw.»ö¥·Ÿ:ùЙw¬Y¶õõ- =qçÞ€žF£±xñb“’½½½h=¤âŒ3ξíÆuÕkµöw-W_7÷À¯ýÝï ³ˆ½Çq“¾vñÉç¿2´¥µeû¶Ú‹“¦ŒyéÏbù£· !fœúéÕKîgHDʽ poþþþ’ûõæs`+8[6·.yä‘ßúÊý_ª.„8ñ€c÷ŸyÊÓOLö™ÁƒßwÀ^ï}ÿnûî3ahä=ëúýè‡Ü|ÿÖ·Žèùö¶·:®#îë”§øqåÞ°°2/4ülj=ùëùëKš¿þî§VŽÚyÔ‰Çÿ˺¡é_XsÙE÷í»ßÇûÄ`ßúºbãÆ7ß¶Ó¢«¶Ÿ:bhðíÈ{Äùë ©õúw.嶤z&ª¬Ö'¾šÙñäàˆIcÅØá£Þܼג_^÷ÜókDkÊè[… ®øÖ%ß¿ü·'Ï9'¯7¼‘Úð—&¤“ ×a¦ˆu¡õf’üõñûžò\†¯Dé-3ybKˆš¦fóLîÙYÛ.»Nü@½öü´=G^ç«sO¿è£¦þó[Ûºà‚ï?îåÿ×eñØÍrx šNG]…[®¤»»[¹¿¯¯/V=ä¯'½‚Jå¯ÿêq‡½ê8!Ä´=GNÙ}ÿoÍ9e⾟=æ¨Ï|öX!ÄνùæÛÖ¯{ê©¡m. ÝÂAþú”(5=®Ð‹X94Ëz¹îY˜ÉMª¤?E,“FNs¦4oÓæG-¹®>°ª¾nu}`Õ0ñ×üõ3g–'ý¿þbÕsk‡½ðÚsß>wòC¬|üñ?¼¼áÉÓf¾ß;¿úÚ+—ÿðÇëŸyòCþÛAïÉ{Š}n1½Êç< ³| ÅHÊž@èEþùpDêxYðÝBT†\ó×1zÛ¶–¢^¯MÛ¿æ-T’uþúü­ú…Þì¾çö[þ¥õ9]Ç{òÊû/¿}ùµWÝ0iâä-Co~ÿ»¿¼áÉ"^ÇÎ ½¡Ê“¿>–Ü'zAþzò×+©TþúŸ-^óÂs›_zuè®%uïŸ:∵kÖ !î?åKí/d©7I¯IÑ–u]y‰‹´ 3š‰qRÞeEÏ_ß××—XèùëS¶Šüõ±ÆŒËùë¯þáV!„øúÈÑÃÇ|³÷›sÏ=ëwÜÑ–ø?ÜýǸO²¸cÏÄbV.kÖ*/È_ï*ü– ÂGõò×ûý÷+/¿œ1ï!Gv‡!=·B_Ofê²CþzÀžÒßߟwNcëi” çÇg„Ö÷ôô˜§Q7Ñi=P~­·›):E õBÑh4Øf›m¶ÙvpÛ¦Öûgÿ°Í6Ûl³íζ!µY3]tÍyBˆÁBˆEÍA!Ä’¥«ƒÁúF£áíd›m¶ÙfÛmf³ùÆæÇƒª#^ÏS”m¶Ùf» Þ} ¿Ç‚_¥Kÿ|À@… §§'¡Ö !¦NŠ笳ÎÒÞ£sŸ `Gg¹þúèE„ŒÖ*™={6ÖpÃßUñm ü õh=Ö×ÁÊÀAä>CëíÛÀ5”@ùAëÐz¨¦Öwwwwww‡íì¡Üv ë yÇÓ›(S#§ï Ò¯÷ßäþí¾¿¢ügɤ¼ôÏ*(ùÍÃi«‰'úÝÝÝímo¿TÀ/@RÉŽè ×à`æ§‘]P–4ßéÿk°…ž©%ÃæÙA(†ÖûõB’ÄzÚ®Dù jVž´Û|Jn›ü5²¤ÉNÖ÷äÜA(R ' iî : kC¦m‹¬ÜâÙ;ÒA(I '‡Ç‰ã­Jÿê“æ¥$–¯Ý‘€‹ZŸOtÅA_22r’E_Ÿ´(€LÉu~}0ÊWƒ\ö;Û¶ÎŽÛPE¿Þ®œÂá½7¸à3úc Ò‡håþ`k¥£4=e=Ê“J%¥¯åÊÆëë·ÞA(°Ö+ocåÍo·ÎŽË}dgõF0ÿ«¡…ÃŽŠ¼æU¥ì 8B®1œNMÀ¯ïŒkŒ3PN¿Êï×–¦´¾ä°(”b8h= õà>Äëuða$Á§D´Þ¾M\óA‰á”´­€â“$^¯Laæl^3ÂØ ,­‚÷ps3*ËDž.ìŠhÊÿd¾ ndS••§1Wé°U®!Ù}Öõ…lµ>8ΊµŠ…¿Ù îù4ë®D®ÜbXy˜:$xðX±j°IVVŸ½ÍôdÃéþ+ÊÒRÒNe1i¿²†d·Š·Üy²Ú“;r6IDATüÍ6´FØÊ!‰_‚†RªXã>±LºW’¤ ªyIgWŽåõgÑ<“Öz%“Ëÿ'sCé-ܯ®‚mÒ‚´æ‘¥ßŠQžË)'+q«L–‰ ˜< _#ò·¶¦³ÁŽèÿùª|ñ’–è’ŒwõöÈ+¢ï‘rÕLüúD˜ãÂëpn7‰áÛ€,Ę,žc1W1ØÄï:D>Æ"„ Îb+Nb~.ÃN¡ãп>¬+‡»æ®Îú©ÓñG‹ÿÄ$Ðdþ9®4–71rvz”ì[HÊ9;Ñ‘Á©‡¬´>2léU…í4A+ÎWgå>Ó:­ûø¶>&§éš­8‰ùE «$瘉¡ñ‘{È$†cKà’}´å+ywQ²TÊ[+ÿ›3¥ÞÁé;â—ª0ÙJ?ŸÇä,vßÞ"Ï˧áÓ+t †£ñèýÒü ªt”$O_úöëŽ žË\ìÜça‡ë¿›EšÂz;5m°%—šÈ›r¿•a˜<üòëÅ%Øe%J6 ‹+¢ì~nO/(.µY3]tÍyBˆÁBˆEÍA!Ä’¥«ûûû›ÍæÔ©SfÏžíT£sûÝVž¹ÏLÄÝ⠻ǡ#§(=Èz5‘tÉÛnçDk6›ol~<¨ê]ÅÙåV“Nå.ú)Ê­òØò‹á8(…U€{âBî3üúÊÃÒT€Ö—¥€r@ ­´܇x½>Ì€ƒ$ø”ˆÖÛ·)€k>(1€òƒÖ õP|’Äëíæ +t'RP@9µ^R·*gUÕ¬ Px¿ÞrI i)ÿڪʗåKCX%ò£ƒëH‹« ÷à, ãõA¥–Öó ó¥ÂÁåu"+é  l0@9µÞïn'^¯9ñ „;jëÏ` P(¡_ïÜ ÷mʼn.„íüKà2’Àe’Çë• "[tá‹"÷Œ!(¡_o×éŽ[›yà(Opí l~½äÎKsQ §Êø£Rmæ•äì¿çáàÝ@iµ^#mÁýþ=Ò_ý‰Ä•ä,÷&Fpr$ õ€Ö€û°VI,Mh}ÉaQ*(ÄpÐz@ëÀ}ˆ×ëàÃ,8H‚O‰h½}›¸æƒÃ(?h=ZÅ'I¼^Ê o%ãcpññB`Ò윳ۓLlúõÞJ„V)â²Ý&ª\W=Ó§ŽY/ ðë³Rå{€$|ÒºµžNù÷×¶ ;*Ó7ƒà)ÌÏÕ>6²/a ë¸dOàÔZß¹·X,·z­€"ÁZ%°4 õ%‡E© Ã@ë­÷!^¯ƒ³à >%¢õöm àšJ  ü õh=Ÿ$ñúàZá¥GÊ_FÎ(¹ÖK¹Úý9Ó#E0‹õiózÄ*ç×=\ÿªÒžÈ2ÊG…t¸ƒ i!À`Ëcí1/ŸÖט•äØ/Xþ…“L”Ë5e÷/ù÷ @¿G³’/`‘Øßf%ÏÔqöŸ´³Â'­€Pr­—”W#‰ÅѤòŽÈ½­E¦ºÿŠæYÂÓ:¬õ’"k¼òÄθ›ro÷±!½¬Ÿ%Ÿ.€ÖÇöI­7BY§ôÝÒYÉC‹À}b› úžÁ˜ƒçJsoÂÊHuº6_Ó_É Òv䘤Ä$pBë5ê#íW +#E3 Ϙ‘ýÛød{CŽ´ÐzpÖ*‰€¥©­/9,Jå€Zh= õ€ÖZh= õWë•yäY>  „~}X®ypû9”‹l(—ûÐ,Ù¡9\° @Ö~½$Á’Èú—™õ/J%|‹¬jvúÿ©<<ò\`A땎vâCÐhwµ>–¬k‚6ÁÂq¿ôò´ÈDë½°‰f!V)äb(÷þ°Œyc‚áÈܯײbbÜNšÅÙ;ÒAÈį7 ÖKA é2Öœ¬Â”ÍHv¸ç³Çú<Ó‘€}­7¿ù¥pAdÜ ·× s ³ë&¶@šfäiöŽt q}~}†Îú•¥?;n;@ñüú¸··ÿK¦2Vnè‰KµÅ ,H^|ЩWžBjpðt†oþ¯a_ 4Q”ÍPžÚ¤þoªÁJ³(«RÓbãÚ:£õ†_Yþ©J%“høÁ6VƒDôÍû8lr”a7#wjfE^ˆŒ:áHŽXáÀOäb õÉÝöÜÞìÖ‰”¸ W “Zh= õPD­¯rZó¾3ÓðëËÿHàK#tŠRå4Q™{ÃΫUB›†M€Òj½#9ET:߸ÛÊV_*¢rt–²å4NFÁ¯:¿ÞPÙ]ÈiœÑ÷Reï"ÏÂ3ʦõœÆ&gL™†7n7ä:Er3— ~}Î9ÍÓùj*7oUÊ5ÇJ¢õyæ4N|ÆÄ­²’ë Oø-UN=4 õ꣕2ê~= õ€Ö õöIëØˆþÀ`™Z­&íLð94xHšÚ4õ[ÿTk^a‚+’²[Én”Ú=ÎõÃ,,çGØ98îˆáà×Û¸í1TÃ\UJûØ}ZWKë ½ìà7Iì/™pF9P_ÎÈ,ÊšbzÅ1©Ù$»N‚dÑqm¨é¦T•Ibjç'ÇúÊ O·ÚX×Qy¥R6Osô7&WÄ<…xä¸edïð°J†º¹ •OîÒˆ¾1œ²†\"û•]Ç­$‹NS¦ÝŒ[¡a2 “×ù`l%°ƒSw«õöX©°¬iNºDeèHÍÊIdQv­›&vHÖM¥»—þÓ¨E9KÓ¼ìtÇÄÚ±®H÷WùbÓ'¯u6©m…´>ÁkÊkf1“³þÍ4£±e>ÖwÓ$¤`ÒÍ4×:Ls5!…œ‡¢õá—rPŽ"9¨[ØWü…>ÿ›±¨1f†ý-'ç+XÁcOÏßneºÉ’ïÉ÷ÚwŠwzG/lfBÊÚ”Y”ý_Œcõ",‡³ò\il¢é‚&:œVÖMU†vИT?`¤f+¯µ‰ÑâæÁNö²Ö<½… 3ýÌ?ÝLÂ1üÊñ9‡"2{¹4œÒ|Ó³áuAëã½½†Í±±²ê¬²r}úb“7è<5{ S:GžË¼Ò©ãV«Í‘ôÍ60æ£ÈÄH°Ç$ñudó ­‘øŒ†mеy¸&îa=Á¸¾@‚Léh=@i£"å M˜w¹:yXÓûï… Ö£õexÎg7O£ˆÍ6?£S××ncò™Ë[¦›ÝV .Û‡Ügå­@ë­´Ðz@ë­´Ðz´Ðz@ë­€<ˆ—Ó¸¿¿¿Ùlb5€Òj}OOO£ÑX¼x1V(1b8F{”ܯïïïÇ^%÷ë *Zïä°Í6Ûl³ÿvZÃ6Ûl³ÍvþÛ ¨Íšyè¢kÎB ¬B,j !–,]Ý®wêÔ©³gÏz÷^øžm¶Ùf›íü·%¿½¿Ùl¾±ùñ ª'Œ×óte›m¶Ù.wŸÐ¯ÈÖ¯€c~=ÙÜ¡§§'­U/¸1´ŠO¼œÆÝÝÝþöõõaA€rúõ}}}m•—¤Êà×›¸ü~gßÿ0ð?úúú¼ MáÄåyçHëׇ ½äì{;½÷yMáàƒ¾|ðÁÀ;@Z­ïîîzÙ‘þ¾Ÿ¸NwXy +­7TÞ`\Å"šÊyXÐz' †k:"÷ʨZŸ!™j.‚`Bòy8í¹1í¯¦Þv˜×ü6+íWÖ AYyÜJÐúh?Z®1qº•ŠÜ©?ú9ðëmƒß à´Ö“¿Àâ&™ç×/^¼t–ÞÞÞà"ãzˆ×”Ü©GëŠG‚ˆz­¯ÕjÊ=ÁýåÀ¤w®Y j×,k}­VkµZîKsšU×zsÁm£ßãí÷—Qд¿@Ð{•— OÖÚ¸ŠNY€g±3¿¾íé{þ~Ð÷÷ïQn·õÈÛÖÖ¼g´Z-ýáR“w©wÊc±€æ¼R ¿Þ>a”TÃSž7:b÷nà¢_o¨mßYÒ©Œ" Ês¥Œ`DÖé”4‹ñ I´Þ$â÷Fý œ! —Ù$¾a¥Îd5djŒº¥Áõùõ¹9§ÎzÁ)æ½a~}Vòäÿž)M/1t6ý†}®TžËû<¯²€¿d°Î°w, ,ù6†¿€ÖÛ‰Dî û,)•4?0eýL}…´@²P)bÇpˆ ”œz´>¡ÛžÛûA¥ÈÈ­´Ðz(§Ö—#_nš<ÀÊdjúÊðëÝýÈòaóè=˜¿%ÔúÜ2ú†U"•Wf6V*»Æ—G¬  >§±aåæ³ ¥_Õ ãßXTίW> ôûgôE‚òðë•®±èPFßà‰Òç(ÖŸ.˜@ù&až% Z/:šÑWó~…ÔÜ$Ö'UÏiÌçVÀ¯O+ 9dôÕ”÷»óaíQN… ¶à˜Z/$…Ü™QNc“="*•q‹ßt ~KeçÙæŸ¹O°Ðú´ïŽ7Õdv)Zh= õ€Ö õ€ÖZh= õ€ÖZh=@Ù‘ÓxμØ 4Hª^GèÊ-ô;üúEל§/"¨êõȧÝÇÿÛ„ƒ+0 @ið«:ópÊZ€ÖZh= õ€ÖZh=ø7kB³ÙÄj¥ÕúžžžF£±xñb¬Pr¿“âõøõB!–(³_ýõ×c&€Òúõ|†(¿Öó% ðm ª~}oo/¦(¹Ö¿±ùqLPZ­_tÍyé+3o•z*Evâù®xýÔéÇ[i+ À)ñ|—_?8°ÂníÖ+¨ÖųŽ(”ž®ì^æ8"žµY3ŲåæÿÏÕõŽ ]úÏIEND®B`‚jsamp-jsamp-1.3.7/src/site/resources/images/ProfileMenu.png000066400000000000000000000257501356450004300237220ustar00rootroot00000000000000‰PNG  IHDR ©GÈe{bKGDÿÿÿ ½§“ pHYs  šœtIMEÛ  ´€ÉâtEXtCommentCreated with The GIMPïd%n IDATxÚíy˜Õ½÷¿==ô0Œ Ó#K$Š*`@…!!^"›sÁh\“ÈE^½^Ô¨È}c@4ä†#è+.€0G1Äe@ÙtÜP‰A`ÂÌ0;3ôôý£9=ÕÕµwUuuõ÷ó<ýt×vΩ¥ë÷=¿ó;ç¦]3, B!ÄeJž{7Êñ«Š‹ã¿K««[eÛÝRhnêê’ÓØð´?®åéçôÀ¨iUŠÛZ[€ƒ‡’Ï÷úù±ïPçÄýÿ±/9Hm¹PúÄ®ŸÜ¬Ù§%_B!éÏšýp‚Á·%MôºùýŸ,Š Rû¾|R›ÖD2ú>Î\UªÛ… ˆÔD’¶µ¶¨§ýÕ¶®8ÚØg ªFNºB!é ~Ê{–ŽøF±÷Ù1ƒ) cgÃ&fæ}œóhì»K—Žœ#µ@¯æ‚EÕë"ăðD@åÛ=g ªÆç;‹c†øÆ=/§å„Ó•/!„ô½‡ζ%žuOÄ›$ä"B©Ý«çñ& Im;Ü#&Þ]›÷«ïàà'?Ý MM±ï#‡ S×mñu@¬)¦.?9ú ¹%æy¢âà!ÄEô»¤ ‘Ú˜f쌪ôx!„G½C¨sâˆ5Oôê™xL¸pù¨Þ!®˜º;É›ÐÔü}ógñß‚ÂÂØ·<^£f_ÿØ‹YñÂó–…±óƒBŒ;6W©0¥K+Ý»©lž „ôWÞÃ¥OÜéŠc«lYZÓ–"„Caab0dð´ýžö4” í‹~¾;i½•Ûë1fj‡Ç¡©©ãü„áìû² NR_Þ…`Q–…1hhv~P}ÈñûCJ!$ýb!](‰ ÖLÑÚó4DjƒñZu¤6ˆpoÞ«»–ý¼ ù!µ=~”pÎâ[xäTn¯Ÿ³œ FÕ`ç…4´.š›®›Ì^„?ÛL~Oº‘~º5o©pÞi äþϦ¼6žNúþ&Ýó*± ¾›[ÚêX<Âû¯íE¿Á]Ô'/< ± ÆäÞÒP vÞcgùy¹ =HØK‚B<øcúö «´½_©a°(‚êÈI = ÆÎð†WAˆ)Í­¿žøêÓ*uoHò¤ú]¢ÞõR~Þ•o÷ÀYƒIsØév'ö’ „d:™þžôë{¸K~²•öœ¿ Ô}ßGԸ݌gžQæ¦fíýÅ9íðˆœ:0š ¤ƒ2)yL¾ÚÖ5)ŽAˆ ùþì%A!Ä÷È= Ò@,–Aj0E»°SÝ{us´l%C{à®%À/~½;.¤b¡K~‡ðBAÇPÓ-6ÎÐÑMRœ£%‰ˆc€šªØyoZ‰Á`ÚÃ@!„d²—¡©9yP#Q^†^=ƒÇÇf†• ¢èSTèXùÚ??Š÷¤žþà" âQý}ä\#ù-ö9xaÙ9S0Bñ-á®@M½zÓ„T8ÇEáÄ4”…ršYÅÅiÉW B!Iô;w‚97ûa̺¹Ÿç= RDÏ ùh……‰£@¦C,x‘Œ ƯP\ÿnùõšû+m×Ú¦¶ZþfÓ1S+å&„;ðK ¤ð2òC1OƒtP'©hÈï¬<³åÖ4•«G®cÆ=Jõ»å×'L-Cî"ÿt–BHê^)Òá£åÃF ³n*À^W•ÔH¿[~½ª¡–®6|…¦BËa¤< „t¢ô^΄&‚tzâ¢Kâ0ÊRñ Æ*› ¼Ô­‹¢°0qZh qBªLä´µk_Oœ˜´NŽt­48ù!„$ü!š šš;š ¾ ÈˆÍ!æŽ(,ŒÍ%!Ÿ*Ó‘Š!ĺÓÖ®U ëa „BÌÐípßu‰©!Nê¸PDjƒ±uˆ ®ø²Ó÷qaøŸ¦ó»ÐBgcŠKƒ4I…Á×'ªz¤ë…  ` „â[ŽœØŠ.HîqRïX3Ä‘ZÄ…‚T<œYôO¥Þ¥ø5ï‚t]ŽÞtú&§ë!"„Ly1ýÔEƒ”üPì[>éÔ)}bŸàÉu¾ RÑ ‚Tk’P"JŸ¸Sñwë>^5ÑÑp:ýLbþ¢eYqžs§OB8æ '¾!Óß“N¦í”«Ú˜ty¤ÍbZkÑ#¢µ%æuèVû}ЇϫVУ’÷Ax'rµÔ`{{;ß.ÓÐߟc8æ³EHã¦XèÚ¨—³ 4½µ‹ÁŽ®”«\ž?˳1}A9o6I`ѬóxˆcœX—8+¥’P®íø­7c¥–‡á£»0zÜt‘%òÀoæãÞûâÓ=Ûññ'Ÿbô¸ Ø·÷äåå9rîjåZ2«ŸûÍcþùÍçø¤²Wþôj|ºg»¶`Ø»w/Ÿ4—Y~Ïp^’UL_PÎw qiì‚V\BSS‡HhjŠ}Rõ0TîÚ–$®zMr:{+”Ê¡›gxî¹øê³Ýú†3Î8ƒOš«ìÔÔÔðR¬‚ïâ$MÍÀIM¡Xœ‚‚gA* ÄïÖàà!½”£¶%ªvîÂèq±ïË“öihhÄÝ¿y6nF{$‚±cFááÿ~ùùù)œ}TW•8xÿ1g>¨ØŽ>§œŒë®?æ£;ãåÍá£D!Äï(‰%/BkKì[_,ÄÆmRûÛ¬µ^¾Ï­·ÝÚÚ#øàïoáöâÀC¸ÿÁ‡5óÑû¨•Cú¹eöí8¡ {>zkþògüeõ ŠeÌMåÌ+Ý‘UœR;ë#Ëר˜ÃÙüWBˆÍÞ…æVí`ÆÖ Ô¹cùûbßu…){^ð£øšþýÎÁ÷ÍW¨ñGQ]]u6âòMèzBàæ™7bÖ­·ã·þ&eƒ´P2ä¬úó³8TU…wþöw¼ý××Ñ¥K>ºtÉǬ_ÍÄÍ¿¾5©Œ–üÒYÕÞ®¨÷Èò5˜;}’myd›#„§"AîYÆJÅ‚ RDcQ€Â”îÿž;°cç.EÁð݈F£{ÅUF­mmho 'Çjƒ€r9ĶC‡ª'¿w|ߨoØ#„Xxoo}Ö>„v‹B!yš’{ö0UTT(ö°5jT²›¢BämWžzéÉÏÕŽ¼¥i–••%•Áîs$„Ø/¤ˆÀF æUÂÀˆWÁ°^P뜠´þøï%-‚ÿþ.1MÍMèѽ;®žt.‘‚`8žÇ€ó~˜°zèÁøËÊ?J[„[o¿çž? ßëÕS~> ;vîN*£aÁ`V,Ü2yDÂò“«ßLÚ&ÖÉ—ý ´ŒˆU.aÔð cÇ–ÇæMË£¯ži¸£Fò½±“žŸô|í(„dËî‰}_}»²AI(ÔTáÀ¦5 .Ò²ÅêŠaРs±ÿ›O“ö“®—ïSPÐ-¼-¼Ïp>zHóP+ÿ÷Nê…çW­H´á¿š‘t¼!Á`U,ÈE‚š °S(8)>¬xäÆJ©lX0œJø]RR÷,Ì_´Ì’1ÕJRj@ÕĆÖ9šI[­vQ'!³©G¼ù…â>}ÎlÂWÛºêz!„wA0úêÁô0d’‡ÁNoÀ“«ßLˆ}p³Y"ÕH}Á€^LEm—wà 5lÖØ !f EPÔ§Ø®¾hšzNýÁvýijÌÃ`»`†Yn”ÓÕ &2¹Ó'!cá¼™–‹š§A*h¤µby¼ƒQá!HJy›I[/=;YOŒ›iB¬Qùvô»¤Js©PbMòù%”Øÿ/)ì V¼©/?VI$¸-\ôŒ…QChuÚk#ù=ÆH¡Þ˜FÓ6šžÑs5›¯Ñò›9GBˆ»ä´X:NO,ÀÐK® `°K4ݦ´¯Ñ1Œ›φ#Äɧ!Ä^†­ÇŠ…Î¤]}è[ Bq %aL±ìã~ÿßâö§ÄÒ/ޝ‹-웂.ÅRٚǿ£~qŸ¹ÔŽJZ(ÎjÚw,ý‚‚Bœdù=ÃyÉ ¦/(§` „¤«19„ô )¤nÂé Ê1cꬾXt›B¡`PAê:Ìæi­Ýp¡N_PÎ'’BHf †l ¼„BHŒ^B!„èa(èñ•Ÿã•r…³U·zîôI¼„$£Ñ{Î7>¿ Æ"ij‚ÆÈô‚*oºnŠê¶p¨ áp8)ò\i²'µuFg°4;‰•”T_ôòá–åeʦɭüŠÚs>yüp@EE…f ­¡Ðî£÷ìÉ#“Içÿˆÿa’~Â!õšÙS«LÏ3áRcžÊdXJ/¾Tüùœ~aìþ–oÝûãË£¯žiú‘?sJ"Sí¹TÚǪà&æÞ„‚¤ò"=!déϧg¨Õ^¨ZÞTkÒã¥ù«ÕÜä“U©yŒx%Hæ<çáB())‰{æ/Zfºfªç]³cFY£Þ9yžfþF¼©x>ôæ³1’¯|^-eô¥ò7sýŒ¬wë^x=f5 ­ª3/µ?ˆÖŸÕLf…ƒÒ‹A«‰AºÝ¬W‚SdgÆs.b¡¬|«©çÜ Œ>KòçZÉèù襥—™2Ù®÷nÐzWÈËhäZ}ïh¥­vŒ¬wë^P0{^¤­ª71ûð˧Ïö›ê&ö?çRV¯+OËs®öìÊ Cª"ÔÊ@+ÏtŠb'ÿß^}Wxõ^P0d9õÍmªŸtÕžŒþÑÓ­²éYȬç\ðÌ eŽ>çV”ZÒÍò˜q‡ a#ýxÁÐ:U»ÓN%ÖOï † ¢¡¥Mõcä%#ué)­óš‘·ûEë§óñûs¾xåz,^¹ÞÔsî´‘v¢×[¢Aúü{áàdO'ÒV»nÙ&ô˜A èÕÉÐx „dãs®¼(Êò}¬tN·Á•Ýfµ6MÒs/(ˆ%æNŸ„p8ìx×Ié U­åT¯#y{!MâÝçÜÈý¶òLÈ…†™x3ùéíëTT¾‘ó³ó¤”ž]BL+mµó0»ÞÉ{áeÓ®-}âN@åž—¥K+yý‹S‡à•ŸKyà&«Ýh²‰y¥;°üžáœö—ð9'„xî늇Áì@'v ŽF!„¤Žã‚ÁJw:½>ô Që3›MC»B!v’½$¬&¤6„°_Õ „B<ía(..NZW]]íê‰XüD~œÒ¹¸ÅM¼É'’Bˆƒ[Á®î)Òæ ¹hp[èH™WºÓ”ó©$„â_ÁàU/‚ÑN†ÿ°+ŸJâkÊ߯çE $q<†A-îÀ ƒY0vB1†+†TýHåxù±f]!„Bˆ‹Mz#¡éíktÌ#ÇR âMô†„Þøü2þ‡ ñ»` öòøü›s.Õ]']6š–‘ý¥=ÖHšs.M*“Ù2)wªe6{^}nœÌËJ>7]7EqýäñÊ£DÏ?U¡‘ Á¥ó9Ðñ)RcnÕ8¨1§Œ¹Y1å¥sÈÄ2Y¹Æá‚F ¿0f8Ê·Æ ÉñåÑWëÏ1Ac“¹ðÞQ0‡Œµž¡–o“×€¨J—æ¯V»—®—«W#ç£uNòe-„VÙÌœƒ–wFÏ«¢tm^W-OÕóvJ4„O%ü.))‰{æ/ZfkMVÍP‰s2ZV›ÈIúÛÈ„FzÛS‰ÍÒ B7:<ÎLoÔ^­´ÔÒ62J°Ùëgd½[÷ÂË䀸-ã¬e|̤‘jyæ,\ªi¸¤ÛÍÖží(¯Zœ—‘sPÊ#Õk+ÒÒ+“çmåú¡¦¡5þ[ˆ…²ò­ ëí¨ÉJGy•#¯´ÜXh•6{#Ô^æF{8‘·ÓiêíïDµÊ ÖÃAk»ÑóÇË·©yœœ:o+éÌ> áp çÍtôÿ 4z¬¨ Úánf”Z½}ŠÊ—æ«vþ©ŒÔkæÚ;y_ÕÎÃìz'ï…× L»fX´ô‰;•{^”.­äõÿ-fL‚W^|s§ORM¤¸¸8>Ë£‘&‰H$‚»ï¾ïmY„ÿE¸8´KvíhÇU7àµõïaÀ€¾5ä" Οð'ŸÊ"¼8°“”¿_å÷ W|‰âMæ•îH‡aÉ’%ؽí!løKB!Q¢ëu€pþy9Øø|#.3»vÂÂBK†¸¢¢BsÙ4ì Ò´9µuöˆ§¼„â® †]»v¡ô‰;°gKB€@ €ÊÏ£xöÿE“® `è œÕ7sU…W^yÓ¦Mó•WÁ B¼!$Sq=èñ¥—^ª§"ÈË  ®øÉOÛ±èÉ(=۱íxî¥(ÐÜrC*Þÿ«c†[Ú$ _ÖÚ—B¡‡ÁA¾ùz~x â1 U‡‡:¶···ÜÑŽk®  @N(€êÕžðÑŠw@ïøi£‡ð©$¾¦üýr^B(ô  t,|5GÐáûh¢½Ýú°vxÜn>`0!„¬ ݺõJèÑ«'PTx\$gìHÄ‚ õ´ÅØgB“ !„P08§ÿ±}Ç >/DüÎÀúçrð³Ú±ÿ ð“‹€?ÿ>hÞÛÅ9.ÍŠ›±üžá ËÙ)<+ÒnºJ„Ãá/Ì#Ë×$uûUZ'Ež‡ÖLxZ³ñ)›ê²FŽ3›¶‘þïvT7¯tß¼„P0è3fÌ8\ueÞÝEnn¬mbØP`oEÇ"@AA4¦¢ÀÑ–(nž[ˆ×þ»ãårËû b!ô„{RG öãTSŽÞÀ6rcmÄø«¥oåx;íQ:Vmv>Bƒ+ôéÓ³o{ÿçžðøƒQƒ @^јˆ8v,Š™·ð_÷,Ùgžé¨ñ–5ê‰ qïz'ô¼ z†Óèv³³òÙ߈!¶2`*û¤C<²|!HÉÁS¦LAmm-.¹ò6¬]ÑŽ½r:âr€C‡¢¸òÚvÜuÏZL˜0Á²0»N¾Ý)q 'ò<œ,ÊH= 4$þà¦ë¦¨ßïP›ª7ÉÈp½f›¬j^ÙêÍIçyÓƒæaÁ››‹Ù³gã’K.ÁCËWàÀ·eˆFöáXä޶£ß¦`ÍK³qÊ)§øö A ÷jP(¸ëY !uÄS«ç™P‹çðÂŒvçMãæý{DÁàçŸ>Î?ÿñ¬¾©ãt qY0œ²Ý jUrï…ZºjÁ¡ZÁ©zFKÍ[¡TFµc¥é«•W/Ʃɖô”HJ¨ @IDATfÇ4ê5’ß#×ܨʈ@³zýŒ¬wë^!‡¯'â'OñÁ½lhUý¤j€ì¨Ñ+åa¦'‰4==#¤Õ¬aÄ€‰<”ŒŽRÚjÂD--½|R¹zåÒwj†W/-#÷@Mœš½~FÖ»u/ÒæaØøÑ~¾ñˆ%~xj¾ùÚ(cü'ÍÔ&­ šuÙ+õ‚1û²uâå줛ÜJÚZ×Åî®·©œ‹åðjSEºîEZš$×ìã¯Ü‚½K¿ÂØ3.ÂÁ ÑýÌv|[¯U¼ŠoCU˜0í\wí|{Cž…TÅ‚Q±Õ4•\›vµE;QöT^X©RßÜ–ÒË]*ì,£Þ‹L@.Æä^µméx.ÓyíN[íúY¹+Æ]t~|ê¥8%ç4v‹¢%'Ǻ~ƒð %¨knFn0ÁcôÄëîD4E  e$ªØà 5:©üé̺3­¦á$ -m)ßC' C6ÆY1T^»^j1^M[«»sºDƒë‚!”ÀÞ_â˦|œ;èT¬}ÿUä½·Ç¢­Àñq›ú|3-^‹'¶Üg_øVQÆüéW&,/\þ = õ–0(¦Öæh6x*O„‘ZŸR»¯Z^zƒJYMW‰½:™ŽGIõå(o–ðC×IÑ`·HËVïï…§Ãi]ûbç×P×t2N*|¹Á\DÑŽöcM(éW‚p‚u¹x¤â<»ö?ñì­ùt(¹@P[ïwÜŒaÐ B“³RM\1^jmîJëÍ,«ýVz¹›ÍLj·H©ë¤Ù¨]†?Õ‘8õĉٗ»¼êŽ=Š?ß—–öP‰^…§!§S›7½Ž_Þ| Ž °zÝQÜøððÔœräåéw½S3øòuó§_©hÈÅ:ùv5!ÏC£%<ìô.({¶‰?øè”ñ0Åî¥tý ¡`ÐáÈá¿!ˆâhç*?iE^cл7ZµáÜþ}Ñ)ÚÏ÷~‰•«Ö jWGöów¾ìZ9é Ȳa¯ LA@ƒc<øèLœtæI¸ÿîjtB\?ç(V-iÄ®]»±ü¶áÃ÷?ÂçŸ}†Ï>Û=qÊ6½ŠÍÿ†û瞈Ϛ¦zþBÊ›%ˆ¿= vdYMS˵-|T ,3XitÙhº„ †$êÛ€Wç¯DN{7´4öÅÀÞci ï¹}0hÐ@T¬Â¶íX»îe‘‹w¶¼»n{÷>VŠs¾Îê19#Ä‚R<q;bì XRÛf5(ËÎ@F»‚¾¬¦KÉ\\™Kâáÿ\†)7ÚÛОS‰—_| ­µøªr?rsB8§?Œ3AäbÛ{»qíÔëñæ¯áÝ„íß.ç]2!F²5¸’sIBˆ”ýù5ªˆhç(6¿{:Îé{*N?ý4|úi%êkëÌ !”›‡Sû÷ÆžOvaí ¯â¾ÇÞÅùŒ°l`…U v”®“]¥íòã•ÒUò:ÈÓ$ö‡< „BœÁµ Ç×>Ü‹›~õ{Ñ—ø÷iûpZáŸß ½O> «7,ÁÚ9Oâæ~à‘Åaàég¡ºk7œ]|bʵr#ëÌì£7‚ÙmV½ ¸)ѳ@±@!> ð_ —ÞÚ±/ǃaäˆq(83€Á¿šŽ[ÿð̘v=ºŸq6ºóÞhŠ Ï‚]ã0ô¢×¬´ñùeAˆïƒàä“ÏÀÒ—¿HZ¿àâŸyÂg’p Ö= ÅÅżpià¦ÞÔÞ~ÝÅõ“ÇTTT(ŠC¥î§j3„JÑÚß™0“׺æ B\Çê8 ÕÕÕ¼xib^éí{Z¨áÆ \ùÖ˜Á;¾<úê™¶”ÁÉYIíìRJCO|-FŸß›W•Xö¸áY '„~—””Ä= ó-ó|­]k¦O%q!ÖiyCÔ&>32{ª^ùîOH/ÉtD ñ‰plhÿb¡¬|kÂúLAjܵšE´fBU3æjÇhå£V.ŠBÁ@²Ê;Áq|r/…Áêuå¨ilMZŸª!÷’»ßJì(7‡ù& $kà8 þ£¾¹-þû™ÊPßÜÿ¤Ó@û NF(= $ãihiÃâ•ë±xåz4´´%|œ0šÙj0)ˆYØK‚d<‡Á_ èÕ)#D t²0§zI¸u„P0¬ó4°YÂâoá<óÝ'•fû”פÍ}#ièÍ0šê¬¦òã­–C{H ’uX‡x[ü¥Rc6»]¯7‚yªm³:Ë©ù¢cˆ/< „B(Ñ„ã0B!† §B(Q…ã0B!ºÐ³@! „‚1 „BÁ@ˆ!èi „ BTq"†Aið#ÛìÆÍ¼!DÜD2'GxÔýÏÈñ€;ƒäxiæÅTÐóm|~™/Γ BÒ@ºç’PŽWºN*<ô†äÕ%jÇZÍË«ÜtÝÅõ“ÇTTT(Þk-Ñ$ݦ'üܼNJeIGþVòô‹H%Æa“ñ•§Á.” ¹ž!R{‰Ê‹’±—î£T&¥c­äåeÂ!L?“ÇG¸ _€ÑWëÏ1!?O%á$¿ÆJëÜÄîûd´9Ëè9«ÍÑA±@!ƒÕ¹$Š‹‹“ÖUWW+¾(ýøRT:×<¼©}OO%ü.))‰{æ/Z––Z·’çFê­Ðò¥úüh¥¥dÄõÃoã0B!6ÂBqÆ0Œ†1 ™Éô弄dÿ ¡ì È÷™ÓIEND®B`‚jsamp-jsamp-1.3.7/src/site/resources/images/SampLoad.png000066400000000000000000000127131356450004300231700ustar00rootroot00000000000000‰PNG  IHDR—·Qß„ØsRGB®Îé pHYsMœÞöœWtIMEã #¶MêtEXtCommentCreated with The GIMPïd%n4IDATxÚí{”ÅÇÕ·çÅKÞŠfAîc¢y‰à("—‡+Êæ€KÄìx’ƒÝœÝŒq³+ É‘Qöˆ1¢Â†à 8:‚<".‰qñ€. ³@DÈð``î£ö†¦§ººoß¾Ý}»ïý~Îý£§¦ºêWÕUßûûU÷íbÓ¦VD•ˆ/z¢çVÏ÷©d@„ðU æVÏWü+à«h…«úß{?yçšü+!|’= ˆ4ªð÷âÚ½-a-/zÂèÖ)Q± #ÙÝHÕa>wvã%9‘ŒPMaË‘¦ã%ðz¢]ŽjsšW7p€œ¶)Œ{”@Å T¨è@ð|ý† ióÌ­ž?wÖ×# b•#– );6M—æÒ¥‰æ™Ëwr–mô£d¢‹W‹ý9Ž(u‰Ù±iº>É¥º“%Zù¾VoDéÄ÷sîþÙxF;6M7닞R9b¹ÔM“úh6uAÂð<$t=ýR1òù™/£IÃ.›Ø0'n£•h:ÔSé)zÔ‰ðZHíˆÒ8‡¥s^HL›'­Ò¹“B«…6=Ýæ_VEI¥~"Ì“òüÕªbdZËt›eÂjåÞXxFžŽQé1oJF½G ï#Ê`œ2mòë¦àÎhÑ–tuÌ…Øyipƒ¹ZÆÊ™Šã|Ú¯îÛ‹BN–íÓ>Ò¼ª!‰I©˜²íd%Ûø_«‡¿Ü…Š€<ðæBñ $=4»6‚*Ùx=þ9bB´K²{‘BíæSì› ˆªŠ™CuçL¹XG·‘TãǬJÆZÌÇæS¤BLx € #Ê`„,Ót+u°9vg‰s;­L²1Õ…ä^-«á×à€àá²Þ̈6P1@´¹QJßÌïUÔš7?t Z„m {UŽ WŠ•„…m@N¦^øËaÓ¦V [ÅÍ­ž¿hÉz @بž3ƬW \'@¤ýÍËOZ`×H@T0êîQ¢ T mðì~(ØÙØ‚NÀÈ-åeP±¼½fà{% €Š b;ܯ‹•×4Å(•ôD9K1Î#"ÎHI’ÒXÓÍÉ©UUUDÔÐÐà_P…´R?jô£X½LóAðfÛ\,wV™ÏҪа)Ða63ëÿ㯈ԤW®#"…q"Î9cŒ8•×492¿e+·CÐ'9óš'tŽç2jT¢L/‡Ô°°þÑå—Š•×4i'öj×ú½[KJŠçÙÚcLQ. Z"E 6ž?ÑZ”Y} éælzŠqJ ‘–¬ç1fvíÄ U “Ó¦ csÕÆ!›¶(cK¥m7žèÄ%±Ïã¤ë„¢¤c¼²V:â¼:a„x2çÝõ¼ýå°¹Ð6CèÀÂR1Æ“œÅú\ÿùä+šÎ$ŽIu*eÝ;¨Å*k:›8Öœê\¦tm+V™k³ž¯ÌZÙBJ,{gªªªÊJ„¡eF˜Ã’…±âÕȰ™‚%Âu2ëì‹’–cõ}àä«>c¤ÇæÎqý=a_ç+ .zÞJ˜œ }XzØ‘W±‹1¢‡o-=Öœ˜·æÜñ ji,ñ䨒ë{¨ÿ_çÎÄÕb–X8¡ì/ºQíïήûÔiàyOÿÔ¬¡í‰¨X¡ŽÅ¼9Á¢Õ›Â€ðc|H%Ø]¥i‹Ê>ñÐo;Ó¾:{ Ë4h5ç´¯‹høbDœ§b±ØG‡â'ZUÆØ…”ºnwëþ«’Íq•1vÔÞoùɸ""Z÷©rúç}–ÜéÑ}³†^®†g©ºZû.¦D´{­IiÐ* ´³ BÍfìƒݼüð¶¼W1ΉˆqNWwŠqž ⌨gGvUG…1Î9gD½»Ät÷ªÓ£ûœûbmõ’3X…å¼\põpÁ;# ³ZS s—f*Ci7×ÖJ×mI-òÁcÄ9µ/a½»Ý?(þ»‰ŠîÊ÷†tˆ)ì³£g~(u]76÷¶Nñ$'¢YCÛ_v¯SV¢=tyŸ…òå ϲjÁ9ò)œw^Ä%ܾýû»çJbÄV¢Ò‘Ó©Çל&bŒñ’}ÙLÕ«Niz—LrEaĹ½cÕ®˜:3]µ):gÎeLøÊÒ—Ø¥Q€1]H1®zJ5ȪdÿÈ4(°¹Gé¢^©oâ|ÇÃ{|R{„α²<¼«p÷Ș´ÓœÜ º×ªsl:0¢\~cµöÖ1íõþöo¬îý£?3¦IŒGŸDã.®‰ýßSÝ áËdgc‹ökð,‡”Õé¡©? Ÿ(Ò3B@cµQ¯\=õʈsÞ^MÄÒÞ{ä”l=›8ßœŠ_à©c±¢Xq™ZÚAQ‹‰)ºˆ)¤Æ.{^©;~^á,†Ë™@S@î#Jâĉϻ½¤OW;•ùòPcýš»ÞïôÑ#ñÖ zziY»¯õé7ôŽqß¾õö+{]«%* 3jbóyþXݹ3ñf¸'§²p@4¡brgŒµ/fËä*s¾¥åíº×~óë”䡇ÿ–¿KéWAÚSkœ¦·ß;YûÒÎU/ìúŸ6LŸýxE¿1U4#žH2Fœá|Q1;>ÿß=¿yå…]ÛÖNsþ©'”«z2viÍ«´„úUP¿ 6ý~þü¯’‹—múiÍÁ‡ðô7QEêôÔB†#A©˜ÅÍÃx¼ußžÝ/=·ààíÏþ85u¢‹“ÝšìО}ÿåÛ7òÇjö?·àzI¿ƒ¨€e /zÀ5®^Ja¡-ûöì~ñ—óÜþËgøƒ“-%ìRPʆU²%óY޵?{úÔ‰ã‘+x®b2üü­5¯ž:òÁÒ|ÒXƘ„éBö­o°ŸüšŽüþº×“‰D!G”€\F”η|¸í½?~P¿àŸRwj#a_~Å?ü}ºQÿ¾ìæoR¯+™QÈF£©Zßܰ~Äè{{\Ù 2ð_ÅLÚräOÿøßïrrâXEQÚHسµ|ùküXqNªÊû_GO=Î&»ì©læ4öÆÛŸ}±¯¨bˆ(ÁD”û?;s|÷Ü¿SÊJÛ’¿]Ç—¾ÄþYû9%ôɧ4÷ø¦mm„°¢œUÞÔÒ¸ÿ3D”€@TÌä!ýéPãµ=þÕ@q-lÍ[üt³Éq;J¿XÖæÝEEôèàûÓ{}àGDyúäñòkâe¥¢¼íû\^ÀÖBàÈ®êÉOh2Ée¡D”xX  ÷OÄã]»K4§¸HžÿÔ鶪Èyq1%ñBvÅðÈî¾×½‰(‹JJä3ó&yÅÅ&o®™ŠŠKQ2Å›§^»tí~ü$¥Rbú}˜Ô»÷nÁ£_ð®ÝzlD VÅL\Ó»Oãá"óBþ-ßd“ÆŠ‰ûÓ÷iSïÉÓôáÇ¥½+úÁ¢b̬b_¿fÇ..ì÷ѽýó,¼y„CÅL´k×aèÈ»Ÿ}ºnù«‡g?¤Äbm„¬gêÙC&†œ·¶ÒÏžK5t×ÐÛ;uîXD)ÝR›,¶øÄ1Ë}¿ÓZ%Udû]²­Ü–´æ9܃ÚIî6£ö»*fQ¶Õ—˜ª~­Oß1“Y¸ô_»ti™ú×m„LîÏqÓÒå©—Wwž<}Ú̓owäõÉ0Îó®ô®ýé¦âîûmÕ´»|Ûoaíb;nÊdcjiߺیÚ﬋eQQû‡W|ÇŒþ[éÒå©–óv{ârÎÏ£_ügê§µ¥c¦>òØlv}_RUQ‰’I¾w-\’Z¿±ãwg?U5nŠÜÝ#b!»Gü³»³wE³q$Ø9+ý [ÓÄë\+gŒ…LQ”ž½®™pÿCW÷îÓP·jüŒ?Ü9ìÔý÷*9®èHœèt3íÞÃ_û-¯{§¨WùðšgŸ¼®ÿ ²ò‰sJ¥ˆ;^ó;Öˉàzcm¯ö‚ó[,rÒ@Ó|1FDÿòî…ùcY‡SLÊÚ üÎ]W_ÿ­ý{>þä£3kö;røB˹T*©(±N]ºtóŒ'Ǽñ;DÔt6yII½´Åe’ÓGãg*óó©×,…/‡7=s»€„Õ.໊5Öt+¯iJ¤ÔÔÅK•$gÒÍu; ¥k‡Òµ¤ªIí&Ú}€èÀ9cî#m'7F”Hò³‰"¦°Æšn9ù~v²©¸¯Úª•oµ±¶ýîßÂYæåyiNéÒÔvUÞœ!Ëͨ=l (dÜì ®Q^Óä±-œ. "'b”‘„e£Væç ‚ŸV;!P˜ø¿7ø%,ÒÝ•ç`ÞG”ùT¿ jD]€/¼Y@'‹*XÚ%*P1€ŠT €H€'-BÁÂe«Ð fæÍœ‚NP±È0¼jl^¶kKÛ³f<èâÄÚ_Á¨P±ˆ‘Ͼj?H(Šaá@Å@”Ѝˆ¸Š©  b¾P1P±°aµ“^v²ˆ* bû€ù*^½á:˜Š‚|aw<‘ÂLP±¬Ü4³ØéÛíÞœy{D!›¾q¬O Ý^(m[H¶Šž_º%’¹U, P±ìÜ4a® f–3£@[™ù=çÍ FJÛbL7ÛiîŠÀšcV±QÃÛäܰe;æ$€Še p9,!øbјùúfEÆW˧}|8wÑw]Âî$Š/fö_ŒZfVû<Òíµ…Óƒws„LlþɄՎmžø§pÄT,¤B&³¬Ô!¡ R—íýMÂξäSD™ÖsÉһɉ™Ãd³^ñ€òãee +è!w‚¤ …'T,¤.XÕ%È´f/¤›WÁ¥y„©.-?HwÌh§p³ÒªEP+PP°iS+/z‚ˆö~ò-®ÝKD‹–¬G×äˆ@A~P=gŒY¯ð $HÑ»‡ä?Ð/ßÀ@Å*P1T  b§äíób —­ÂÕFæÍœRÈƾùP±2kƃ˜º@£öÅW yÀ8i>T,ŒÅ/ D””* b9”*Ã0P1|µøb*†A 0`TÌU€€A 0` bQ&žHezÊ­•·èÇïïØ™¥ZiÙ—¶JsÒ®p?†Mö¼7*6·z~ªX2³A9|H%mÙ¶C1ú±·få¼íîì ¾]~—Ÿé€ñuØxng>!蕚¯æúbëgmز]?5|°žhLÙ°e»~ — g–š¡ÿwøJ¡4¡ i³aB¥Ò ‚…6ödÓ.ƒµ~3g³iš´ßr>`ô昇MÚæÛt¯Ðd«‹ »¬bÚÛøm2E6@à.Î5|pýÆmÆ”Ñ#‡QýÆm£Gþ«ýiL×3ëÇ‚Z~í@û—pŠÙ!^¦a6„¢Œ)ÏrØ.½ «~‹'¸U±fÌå‡dÀX˜¶ù6ÅØ3V·`1땚Vç Ç[Û°u\Õ0}H­mØ*-ÍXìÚ†­Òt«cóœœ2zäÁ*+äÇöUXÙ“e»¤éÆ~ËÞòœGjNNÌtØä¤"ê—]V1mg¤(W×o&¢‰£o#¢qUô?54s8«=W±Õõ›¥V91,miJFíµ*G³9í¹YZZsÒ|û¦Ü¨Wù|2‘téx¯\·é¾{F%¬\·É\l¦Ç6æ¥=E·jâèÛtcœ–6ƒ4%£öJËѬ•v¦Ã¶;´<çFz¢ÃæÛ7-àˆ(ù­b™}e=0~䊺V%HK³ÊH¦?ÒÞ !=m]D´¢n£V¬kÃlmìñ¤]6&YëÐò\ ýŠÜwÏ}ä£(móíÛpD”|~Ì/™â}^^³áñ#µ½¼fƒž®Ní£%«0뙵©Æ…?õ?î “f0'ÚØc>ËE»ôé-˜gSlZË3½¸~ ©©æ«cÕ|ûö¦½¸ž7?º°iS+Í;†/Z²>ê [¸lÕÔ¿¹_SbúÄ;ˆhùêwý(üÕ×W¦}Kb˜´Í ÕsƘõ %È13§Ü©/[õN¯ ÖÅÂQ⇟Ú×ßÉ%Ã€Š…Ïà0P1øb pÀ€Š…oPb™`À@Å D”*–3Þ}«`À@Å¢Jï! 0`€¼¢¨@ÅP1€ŠT  b¨„ù³ûÕsÆ kQU1aÓJ×Ì­žïUQ€èâ·(þI. )}1?öÖÍ¿ýzᑬî¢ê·×‡Ðà«°iS+Ñ¿€èòÿFþøhÉ:IEND®B`‚jsamp-jsamp-1.3.7/src/site/resources/images/SystrayMenu.png000066400000000000000000000140131356450004300237660ustar00rootroot00000000000000‰PNG  IHDRji;iùbKGDÿÿÿ ½§“ pHYs  šœtIMEÛ "F‰“tEXtCommentCreated with The GIMPïd%noIDATxÚí{dU}Ç?çÞ;ÝÓ=3=³Ó;»ì²³ÂÐME¤ ñš|l%jYKSRE(¢‘˜e±cÄT|`Lá>,A RÁÄE&b‚ĈqKa•Åö1³ììι÷ô½ßû=¿{jïž; ‚ ¬x¶~ð:%¥ÐzŒáä~@)Æý÷Ý)¿«Pr=­‹%E ‚°¼q¤V.~â))„6ç¹[/–BG-‚ ˆ£F§§AÙ(ËF) ”ä}R»299‰ã88ŽƒmÛ(¥PJ®qÔÂ’’ž:‰;3ŽÎÌbŒ–isŽ;ÆÉ“'I¥R¸®+"ŽZXÌŽawöŸ«Ú‘ƒ’L&Ù°aC‰«D¨…%vÔ:šÀG-ŽúØ1úûûéíí•iG¡ö?™1%ë¼ß›•_3Ò¬”NÓ˜O~Þôòé,D¹xÑ™:3‡1.Æ á"ÔA-æÓ‚Ä›^>çn½XZ¥ÌƒFÊojjŠ™™2™Löz0Ò×mE u5Qó/_hZ šuNKUÙ< 4·_Väå#ÖZkè%ôËÄFcYùý¼ûçß6ûÓôn”_µ·ÔaÒ “N³ÏË¿]µ|+Ãr¿S~îÖ‹ •¶«Ö¦7hÿ0ëD¬¥LÚÖQû`#‘ßßS’^PÚ•¾Wû¿Öñ†M³Ya“JyúÏ?¬ë^î5¯›ó;»ü÷ ?ñT(×WiÿZë)“¶}ä…"(¶$ÊÕbÔa–‡]”_£iwµZ„?•\…ô;5¿,•0ˆ I™ˆPWªzŰš#\ˆªýB¤¹ØB¼œB‹}óWªÂç]¹ˆ’ˆ´õ<k=Õôf„U#Í¥ _òT }ˆH·;V-ÑXHñhg½´sG„ ’‚ˆ´8êy:¼01jÿ˵JûÕ“GкZiÖ“g½n¸žZH³÷_jјOx¢ÚþíúÈ6­,´—±ZD¨²Ú^í%\¥ÿÃ|o$Ͱ"ØÈ Ê0/Pë9ßåêÖ‚ÖWÚÇ/º•¶úh~zÛ%\ðñ'A»íb”Ê`Ù"Öúa¹ðßxÚÅMO¡ç¦Ð™Œv³N[G-ÂÒ“‡ýö‡¾1Ëí̺+§Sœµµ ËM¬Ï»òtf¶X±¡aù‰õ9W|­d¹ˆµµ "Ö‚µ°Ðܸõ,Ù¼y3ƒƒƒtuuaÛ¶L  n«,Öûöí£¯¯d2I__ñxÇqdjA–“³Þ·o_ÉrkjAD¬jAD¬jA±Zé™(-†& ·¿ýíÌÌÌ0==]2'¢ B]rÑ4º¬‘müj³¦Õª4…V»rÿ}w–üù×-Ö1Ô³¼‘í‚¶©¶¬ÑsŸo™ù¯Çx€‡zˆGy„'žx‚H$‚eYh­ s# +‡¦‡>¼3Ã,äÅÒêÏ.w‘ÞúÁëj.[Éç^é|—¢ ‚LƒmÛØ¶M4%‘HH$èêꢳ³SÂâ¨ç'ÞÕlÃï…ÝŽÏ.…ë®æÆ+9Êf¹ô0éT:ÎF\AçT«jÕXª»RŠgŸ}–Ç{¬d(ß+®¸˲°,‹H$BWWÝÝÝÄb1jqÔÁÎv¾3¼„™ˆv>‡v›xv!¨æ°+ý¿œxµcËŸ×|Ž1L9=L”‘RŠ#GŽ0<¡nž 7ÃÉ·Ú1¯½”ÇÑ ñnöî¹˲èp‰ÝÝ]¬Zµ*pÛT*Åää$ñx¼ðRQ\õÊbIšçåAþ¯hÅcžO¸@Xzò¡ŒD"A<#ãºnI¬zË–-d2™‚«ž››Ck-…'B,bAaŽ0áÿ“¿œ€¸•ù…Tê /,ôC§Òq,ÆñU;˲øÀ‡>ÆÃß»—^õqÇÁ²¬ÀëqnnŽ™™Òé´µ„>š'ê•Bþ‰j#Ï…Þ¿UµV"̾ -rµâ¿ÞõK!¸ó)£|+£|ëŽ:%Ü®P\×%•J1>>ÎéÓ§èî·—X,Vö;ç®%×Cë166&c}B+bYÑh´ Ì‘H„h4$ô!ÂbW…•*Ĭ£ÑhA¼«µø.l?==è¼…åǺuëD¨¡•Å:ß•< ׋ÅD¨[©%E ‚ B-‚ ˆP ‚ ˆP ‚ K„ÒeXaY õEozWY[ž©©)FFF8tè'Nœ`zzZº¥¶¯~ÁœÁ[èéé!‹†¾”^k‚ÐÂŽÚmÛÄb±Âh]³³³RR-BjꃃƒÄb1’É$ñx\ša ÂJjÇqèîîfݺuôõõÉd™-Äìô0›7o²ífãñ8‰DB”„•&Ô–eÑÙÙ‰mÛôôôˆH·#Gû Ž:ß{Íqœºu¥ÃξÓÈ„aòldÿZÛT[&óp ËZ¨ó=žòcÈE×:D£Ùùóò¿cÐg3X ×D-A–ë^XöBÝìòÃ].–Èù¯™J®¸âfîÊù‘[©Öàõ ôë=ÿzæëChªhM±XT+43ŸpF_êóD¨…6ßFC"î;ß\=ØÈDÌaÒ{þÞ<+} ‚µP·(-´73OA¡„rº‚ ”#c}‹ÊB:äzCa–/ô¬‘Œ ŽZœ0ëæ#Bµâ¿ 11òr:A„ZjЉwy#­7½zó ³ÝBåUë8=ÿzÒÚ }‚ ˆP ‚ "Ô‚ "Ô‚ ‚µ ‚ B-‚ B-‚ ˆP ‚ ˆP ‚ "Ô‚ ‚µ ‚µ°²QJ•üù×5;¯z–7º ´*2(“(|afëAµ°LEÜïbk9ï uóuÝAé5#AX–ŽúʯL‹MZAüÍ¥ “n¥Áíë™M{)'¶à+œ6*>ŽêÅŠw€%^m¹sôë}8ѳVKIe¢ìw¦ _ØýsbÛv%zþìäzì gc¯>ƒeKÁ,êÍÖy”îä¬êr¥ð„šÂ·Ð.U&¶]x¬ˆÂê´°ãv—ê´E¨L|+Ü+:H” •ý,^üP¸' αãâDVÍ #+wÂÅØiˆ¦ÑéYTÔ©C¨MÎæ™ ˰]eSuæ&`KM…﵎Á¤­ª‹²öÍØã‚…›[e¡Œ®}¶ ,o6oŽâÌœš‘+XX0KôåáF© ¸`2“1`iŸ"ÔcMê÷¦Á¼Ã®3!¶3uæk‚ªƒ¾rV(Ë-·ëZ…}É•·Ê ¶VXÊÕ°²ÆÛ*äÅÉLÎÉ,”…!jŨóÂXkòÖfM8»\'¶m©ß5m0s3«1)\WÚ}5+ôá½æ´çå»6Y[´]n¡öн*†>\c@»@ŽÉÈE- _Øuµ„±Ñõ 1±­ ,P•.Ð]+KQŒvTÞ%Uîȕϥ{Â(ŽÑrq B;0ý¯Ÿ+ùÞ¿ã(áãÄÍÁž;Mdô× 3Ìö…‰õ²imòǼ‹ßµñG¹-Ïf¦LŸ+EX¤g¢°$®\X|¾úwŠŽ8>;ï0Œn_Ÿì]Ë’ÿÏî¥ÿçß!‹‘H$PÑ.öw¿‘‰sÿïŨ6mRÒÂÕji%í¡Šukáò·AWÜðÍ{áÐŒn[K_N¬ t“N«ƒ ž›æšâù‘¯_ëò®{fxEmÄXå¾ï3™Çyþà!~ö‹g‰eŽÑÛßOWWñxÇqxo÷!¾õÂ÷Ñç^ŠÕ½ºæëÊÅT«°ù« mu«„G‚2R•ṙ>D¨¡í¸þ›¿¼Þð¦-šç€Ñª fôûw_F èTе,ög2l²,œÃ÷ÕÉ%ý.Ç·ÿ-?ºíËüÞ® u]X’G÷Ðωœ¥ù³k.cÛ¶‡`ãÆ?~œÞÞ^’É$\pb??ÙˆéŽÎºÅsi‚ TþÝHñ¬öK·ò³·… R(c0¹-ŠÍ¨MYÞŽô „öuØ7Vñ'×F¶¯¡o×0 Ð3§²Ÿ@Fe_Íú;™¹ñFF?ÀÙC÷qäÝWsúÅÓÌè¿â¡/|šß}¨TFÏ›:Äu×]Åûß%kÖ¬áÁä™gžaïÞ½¤R3¬]»­5¿3áGÿÄI¢"ëêr´£ÛÖпk¤lY!Üâ[f;ïòU¾ý•o=@2W9¹mmöûÎã…£?ù©ìùôÿÃÑò“2096egì=WGúú B{°ýsšÁõ°ýãŠ~(+[.³xâAÍæK FgEYÅŒ1¸ÀœÖÙpˆRÌe2(­±£'{ñ4Îë7²áËŸDÇ\v¼y?Ûž~CÑQ;Š'ŸüãããlÙ²…gžy†##ì_ÿN¦¦¦²Ntônê4VB…v¸c7d{wiO бèÛyÂó}MÉ÷ZÛ-ïÍ}÷:ê¾' ŽúÔ¶µ$wãü: Jaòaµ¥T6t"+ãOª1t¾´³Ô¹mzMù…sÚ& c7†SZ³écã”1LËt#›ßÁ¦=Ÿbúð!lk†mo*IãØ«<þøÓÄãqFGGyòÉ'y饗°¬¬Ë\»v-Ñh4»qzt¦®s à e~a¯¶]µýðjç0§nÈ:ëSŸ:ƒU·oÊï&Žzrÿ}wJ!yu´ô{$¢rΔÂç˜Ö8ùaj=îîëÒ­s?>É/ϼ–YÀ5†O~øœ×ö”4^xùÌ×óðÿ|›MÉnžþ0J­&ÝÄðð¹æšk0ÆÇ9óÌ31û_ö7M¥}&¶Øq¢l[ïci|{V¼{wœ( µø÷7þ<=N>m¬Üò짛뚟wÒÆ ÖY£ ʘ¢³Î Ñ%çåˆ [Y|ú?./¿ÓS¸©æ&ƒ›AgRbÌaé™8úïô\q%D:™}ÕŤM¡ª¯<ªh0¤öÞÄW?¯P)úûÝš‘“åézk`AÍ(Çsâ‘ï:b œ>[S)îŠÅˆicHi@ùZlÌ®ÙÄÐ9oáœñ‡Ù´i <ÀÖ­ÈÀÀ/¿ü2cccÜþÀ÷aÕàDøˆ9ÎÝê nŒò…¡ÙÆË-@¤óßMÀ2ÿöAûûÓ÷§‘O;±ãDqÿœÖ†X“ëÂoJÛu5(e\¯£¥^é(ÛAuı£«²ñ.wV ¥U~»ŽÎÀzˆÆÉ˜ z΀j€ ëÀkÞ>z•â–[ _s1ÛÓd9¨ ï¸1¹>Ù•ü ÜqÞy<ó¥TŠG¿øEú€‡³ÿž{‚=cîì·2sà ’É>ÞñŽËذa–e144D2™äW¿úGÝ^ì5¿…êJrvOœ÷÷ÅÉŒNµ«®%²ÍØ>é7úN m€åtáÄ×bGã"¿{‹u´gã¹X±æì4fÚ`üty»5¸^‘H—¯]­ØÿsÃÃûŠË^w.t8EG=6QE¨ñÞ^ÜÉIÞxõÕÿîwùFOÉÕ«Ù´{7ý\Àï¾;¸ZBÅz9Ð)oM 355ÅÐÐ$“Ižzê)nº÷Ø/Ä9ïR®î™äé!¸òµg°ç—#ÐÝÝT‘õ®«´Ý|E¾!/¼`4@®«¹6:;S‘q1–Ò®8ê¶¹Ù-T Ûr0‘ "ÝJŽºcÍzT¬kz·Ë¸žQï|Ú›win¿Å¢§[å„þùv‹ é4lz ¬Nª‚£>9jxßGt P»Àe7ÝÄ9—^Ê® /ÄíÇ¡—^y…§·nÅÍ…>*ŠŠÑ oå›Ï‡k9Α#G8x|‚}ǰß@ÇÅᮋ8úRŒGÖwó¾ã|?„HçÃAa‹Z¢™OmfÿlÕ4÷Ù`Qƒ1ž»1¯Û–m—ä!1궸Û ÛS'¡n,«3ñ8ª³e–ÖÙ›ÙKN,žx¾ûæúk-ÏhƒŠ‹ÞPtÐÅ{Þ°ãšýzv(¹^ÆáºÇcnb‚[ßùNR&;Ê›Köå¡›û?“ûÃR`W˜M>ÞÃð¹ïåóŠ{tRÐÑóº pÎ7ƒ)wÚp×)Ã-—tñ­ÇC_åa£sË»^-®ËåØýj@…Ò³ÎsŒ%Û†Ø?߃ПN>B“Øý*ÆV¥£äèÉ ½ïkJíuéÎR(m Ýê™E£-”ºübZæ!‹•›‰ÅÒà(Œ¶s}½=Ï\z¾9Îäµ½Üð9pÍG¯²ˆÇ‚ô©)Ãíÿ¤¹ywö{Ôö¶‹1cØóž÷0麜ÌdйgtN¨ó"í¶•ý tÕjÕØÑ‹±Ö¿Ò³`9¨xOèãæ¦øì[Öò¿û‡ùÏþ1ƒ qŸ%Ã3GhN¤ ãNke®6öwa–«Ð º¤g¢Ü¼‚°¼) á(L:§Îª²ÓøÌÍð“ýšÜaá8¥7¹ë®ÿkÍÞƒžÛFË5£»oøv˜ w€Ñ.ìŽ:{h$’¨D²dùnàºu1¾r–á¿~:Ì]Ý=­Yé3žOãé¼âoIyZß• ĤTö ¨”¿o•£ªÆD÷Œ0ñá^îÞwï Þ¸çÎS¨Ë+D[6 z“á›p* ‰•‡cBðõ>Ãá“üù˜f¤7Ñ:‚ìÿ…<¡Œ÷»*¶Î1ªô‚7Üá—a Êóà“aNaY‹´*ÞÓ6(­KÛ߉††Ä½ã…>/ ÆP:¤„"p@C"‘"Uk•©Ö¹~ÒH4Zµ_Dë]6Nå¢k‚vþey&(ëA“[dIEND®B`‚jsamp-jsamp-1.3.7/src/site/resources/images/WebProfilePopup.png000066400000000000000000000164331356450004300245550ustar00rootroot00000000000000‰PNG  IHDRÆqfÏŸ³ pHYsMœÞöœWtIMEÞ $crmšºIDATxÚíß‹GvÇkîܱ'R¤ì_à„<­ !oflc­±¸k¡àdã°mdp?H $ÙE˜@BöÅ($yÆA´lâÀ¼Ñ:Vd®m´6Þ?%,ÂCÈCvÿ‚}°bƲ¤`ز¿wQxc± @= D©®ëâìæ‰?úU”ž$,Çú:Oü ôxpDå,=·¥z"Æ(MOxâP:H*’ PK\3çË¿öÛÁk.ì^ºðÒ—G ©;O_m¼rëæ‹Ök¯[_4ß2˗ܬBhIDcó9¹ŸºÆHª§U/ük‰¹uóÅzÂ[E°#UùY«(§±A3i> ÿQ±<0öÄŒ·n¾hÎöú•§¯ZXkôê©kXAñ76w¥sX¹GkQO’ª2·T—Eëä÷,á{Ž1Ñ¥¹KÔ/OcõºÞr`îuÔ’mþá©‹-˜ùÊ}Ü ]¬ŠÙx1xMPv³ê²_úý­µÌ,¤UýWêjŽnB91fò„ME/ü{PÕjþG„HæòÙ¿{ $£5·*³ªÝºG×Xk4šj±ß°–`ô’jÕë ÷«Uw}q­‚ûi¬°Fô&¹fjës0Ií',—˜®PnðçNIÛ¿b6Ì@²a¤²u ’Ô|Ÿ!’•f0ˆÓE¡dEH²¬Ž Ò…¶çv¤ˆ¤ÖK`ëck×’yQзA=o÷ju½«7ÙóP+®íæç!*À¸%Õ|ê7«ëGCý˜êzôä×Sc=_ϲþ´ÇßLÑ.ÀÌ)å‰ÄëÖÖÿw[KÚV¬K¾Å!yKÞdOC@¥ÛŠ%m ðÝ)˜; ·bI^Å ÓYøÃ°œ¤—TëéT©6&óS3\J+§!ž —ž–v$!Œ—ù•ºqöÌNãTë »—ö^½Î ð°{þ´)ž ‚J€Të§ É60.tñäKTÉ@RT$I$I@RfMÆLT²úªRêoߺ¡”򯯬ÿ€¤¶ÖÓW®<ª”ú³gO]zó_766‹Åb±@UImǃônnnnnn.—Ë䪺Z­¬¯¯×ëê­õzÝ¥XÏízù]ê²88i[WTÓÚÚ㺸„F¥²!y[JëñqKêŸ~õÙ*DUJ½råÑ—ŸÿÝ?ÿ§×¶··7>'a]uŸ%ì¡FC±ã/°†g8µÆ5fè r%µ¢V|òÉ'›››U”Úóòß”HOZ¿e†iòÁg^ß°Áüg#¬®ÿöl‚µF—%žë-Õ€ÙØº]Áä–˜&yš#é;‰amÃÕºj—Á®¾Žè¦´c&XŽŒIfŸ¤ƒ$“Å?Šü5f%ý=D­Õ¿ûÃ?:<<üôÓOïÝ»gÕܬzjÎs«V6zb½^7:Øu‹k ¯3Þ38ÖŸcÖnŽž×ý­«n1«ó§~ÿÈ-‘7G^W„ëâ:4I»Ž¹ AG%ì VãÐ?µûÑÓô’ZËå—yVÿ¯ÚQ­$õþýû}ªj!+½›åÜøÞ*ê§¿:6'I]fç¯ÌÚ®¢¶â"Ÿ,r îm„'–T=Dý‹ß¹RýWª{üÒÝ»wïÞ½Û \›«°™Z×g{®Ž¶ÌÊQÖÉbÝ«)aj§”Ô P¨WÇÓë}~,OéÙîlŸSÑQ.Uvj§”TsU§À@u¨È´Þ‚T¶xfI%fŒîÍž›,ÞÎí„d’*”ÈÕº{„þÕ¯—ôbÛë“O]ëÓ‰ÆjÜ×:ëӀƋ®€Iü,Ä_—ùz„ëz–àBêò8*a»ÞÕ}~qBju|Ju¶uÄ ©õÏ¥*¾ôȳÕ.ªRêÛß?÷³Ÿ¾Uýýò¹¾õ«GÝÞÞÞÚÚZ,H2ÐÓâ«äï«-…Ù£Ø4`g#áGK}Bª.ži¾—Ú*Þ<<<ÜÚÚÚÚÚÚÜÜìÿ;ª“QÒ¸…Ïœ'Àœ¥jVcfØá”FRý»¨:¯\yôås/}ë;WïÞ½[ó•ŒXs¥º«ð 6³GêFæNr¬»ý!êö‘…5P-çÑ?@A’*Që@uæþI Q=¨Àô躗ê Q¿ýýsž@•U@R[„¨ÿðW¯UœýËo¸Uý ÿpˆª”ú¯ÿøßê?W ÊŽ*¥ŠBT!IÕñ~{9mJö É·â]?%R¡®Ze8-Ü™“i0Imû ß¨&ÙQÑÏ‘ë^’ÿØu£'úùŸ÷•ïä …‚ÈÙГ¤JBTÏã©´j\bvkúakܧ¼Ù××» ѳß[sÝwIÉîy·Î!ß›,­’бwZ9_O³o¨RœM@x;"÷Rƒ!êÏ~ú–þŸ'PM»£jÍõí Ðü‰?"~>o-DϦ£BGÆÓ6IDC¡úÌØ6|„CäÎ×s u6Ì=J­„ïås¥2"ù£ÿ Fô¯ ù61ê€ÈÞ…8(L«"ô¢Ï¼ôçg sÐÁìþ}ýÍÃÃÃ;wî|üñÇwî܉;Tª:àºÿ9Ý£T]_Z% KµàíhI— l­®‘ž.­¾û·bÓî$Œe’¥Ö(¥–Ëåƒ677cö‹årÙ(³c”12³Ó§ª*ã°Òà)==ï±&tlŽaÐØˆ#þ{©UŒùðÃooo=zô£¨§>üðÃÕ’ØI([9D¬»1=“Û°`ùå|Tpž’ÓÅb±µµµ½½}äÈ‘cÇŽ?~ü—¢8~üø±cÇŽ9Rg¤î(¬Ö\ß’%³>èý§2x^× fo»¨÷¤¹³æØwÙx1Ó!¬.ƒóõNpø?àÙ0%b²úWËóû÷ï×IOºìVÏ£‹EýÕÔ¢~™ZÔ„aöâ(‡dYý+É«°£žêªZË/ýý+\ä‡@ÉD>ñŸü•0cQ cÓôT$I$I@RT@RT$T$I@RI@RT@RT$`†,qAB>üÉ'8`<öË¿€¤Žµ`á €d ©H*’šƒÕj|e¤& Þ˜c”Z ôè&E›·^¯ÑV€1'þ«Õª «–5äi½^×ïÖë%èïêw¥\Ý*Ó ýE½ÂÛ€(5>š3C¹õç˜ï6ªq®Mq¢U õê*Á¬ùbmFÃZÏí k¢ÔaTØü[r}&õ;¥†ÕÕj½É&ÌTR­ªÚqoQß7ˆ^û< ©SXΧ¥K´ HêTÕúЩUÀÛQI­_E¯ÂÏóû ­n€~Ø8{fgï¢Rêàö5¥Ôþå¥ÔÞ«×qMþäÒ¦Ìd.ïž?mŠ'?H`ဤLRP'†ÄþH*¤gS,ü€(•U?¶èDRYû@²ðˆ…?@2T$IÍÇJ#x™¤¨ŽÆäkfou%©Âz¯¼@É•d$5Ëœw‰¢#ɚ鸑$ü ¢]¼ÑÉ„¤ã$5‹žêLOèWŸeF «ŸÇŒRõÛ­£Ý† ÊH9h†ØÖ€ÔzŒ•Ùküî¯Ú_‚¿ ÏZÁZ¯Õž¶+^ ›¹|‰Ês~ªä̾úüQyŠRýˆÀúè½ú`>S ͳÛ_®  d%U{<æ±Êz†k°Þ KƒVE\ €¤¹6lÜžüø¿$“?®¡tG^/ÊH* ¹b†‡ U;aiH*´wá_Â7¢¼T á´¥LŒ)<ž²0Õj·}š‘üÈħ¤ý Ïš€(uUÕe®Õyõ£§~Œ´>¡jœÖç K…«rýv³XÏQØÖë­êf5RßpmAxZäº]ý¹á8?ÛšÄç'‘Îિç=‡·”Côq~sßK5÷!ú“gÌ]RK˜ùq_ÉêÇòþ¿ 0jH›@”Z*$ö@R! ¤ô`áD©¬ú °E'’ÊÚ’…G,ü’¤ ©Hj¬gŸè[Ï Éš'…ãüü÷rœ_Ÿ­Fq a´9,ORf÷Bº—0‹(UrÌ_Ï#˜ãüÒöoCŽx­qÒó\cáŸ1nå8?UÌq~AG¹üï9ÆÑÕq“$öKëb§|äX-—œt)1Ø3N„·KæÐàŽð%ªŒÍq~’zû?ÎÏÿÉär£²hÐÑcþÞ¯3Þ«ëh§däHƹYµÄ`Ïølåêà¼ú Imùûó¸ôã8¿a-tUјíƒ{¯@;ƒUçèÓ.9Î`$U™!Ò¬><̶sœŸ¿™FˆÐcÖÚ­ç d²3÷È™3:tº>eì; žý8ŽóºÎ¿>ÍÇù×­®ciü áÜñ¯ÄrhÀã©/†ï¸žÂ+Xîr{ÉÎKÇq$"QjLüÈmª=3Žó+ä8?—OûôËæàáéHêTç†ÞŠà 69Î'Bf~zfž.ZN¯G­ƒÞŒdꬅ uý°õë®ê¬VU7îµ–c^£›m6Ùú–µE®V{&‰§"ëe®æè5z:E"dþ刿~Yô”lõCЙ® \>±¾â¿QÞ§­L’—iÎ>a0+ì²±0©½TýãQØ%u¯›Z®ã{M96/¶NK— ž}×[žÒ­:Jzø›£W×ÖÏ­œ¬Nèa_H†Õ³<¯øolÛ§.›ÍBä½&Ÿ}òF!©¥«mÕmþh¨ËÒ¸íÅú?Ó#IiŸòJ%È;¥çŒhM–µOKh’ ¥ëR±Ÿ¤­Ñ_Z—ê’{&¡1’=«’{MÞ($ìk=TUåyªãqrÇêÒv_rczSÕ|Ïâ„BRG°öo5>&°‘¬3E"™:\27v7N`,—º 4>H¦ºçÞ„:Õ°Sø–ßæ¶HDEÖÛ#>«"œì©ÎßvyÉÇ,’2ÍÐÏì ÿ+®îkÕ§Áao0Â7jŒlœ=³³¿wQ)upûšRjÿòRjïÕë“Ð~¾7>^ç Ò¾æ #e÷üiSsžZ3«+Aþ|yVO9m¹+qJ ³¢ˆòAwyü#ü<‹n—«(³ÕþrY.l‘'ÿ«HjYj[T×'ÌÔe)ªº´G-$ékÉÁ™’‡"FH*›VåoÀœçSM·>…3½ÔÆiŽc­'÷å8;«¨ªç$J%XëlLØÆLB0±£º/Mˆa‘ÔR„F˜x\2‡%9Ï%UÄÍyÎóŽGtÉ!/qW÷>jAØÆ8kå†yòÿ#Se^Yý¡ä}L…q1÷¬þ€Häf‰ `@Æ%¦H?!J@RT$T$I@RI@RT@RT$I$I@RI@RT$T€X޽«ÕJ¬¯V«G®[ëJXNîòGуÕ?{°¿/ؽ¹wÔcrÖ’º^¯W«•®¡™ô2Í(ýʼnM¼è†dòÃ|t IM¦ª =­?“Í8¨ú§gYïu ÍTƒ kù°×úaãj¦ùºç¿Iõeê[<îµ6¤‹çýa²«kížž5 ñרÊ]Ö.su‡Ë*Ï]zEzù’Ú…c{bLy/µîusô´º×µ«`J@ÇŠâbÚÓöÉ,Ùl¾ç• IuÓüñ‘~™0’êây—÷¬ jq´v˜Ž•¸Ë3]ÝaºW8®¬$’Ú%cI-z…•|Éo'¥=úïs?$yEù")ª7¶UäV6ÄܸKÿ'[3R“Ì´9¬\ªm“~´`zéè´#mØî˜á2ßd‰ ™óVlœ=³³¿wQ)upûšRjÿòRjïÕëðÓ[ÉŽ½®Ñ}­2‡Á|·´vÏŸ6Å“½Tô”†d\ê¢}sI8=Oé|ÕK›ðÃláKTH*’ €¤’ €¤ ©à#Õo‡:–}{òß>1ðüôà{©y§g •Ó¶~o3vŠ:ø ¡“™‘TOrrë‹q)ÊýÕ)ÇïÁ]ù˜ãõ+G~xbåøuy«V»nl$W²ÄøÊ–'ßßžÛŽ ‰‡]®hÛ–¶‡Œy¼gíMyEÝOC0 p º^À¿ˆ`PÏ.ÏXÞ*Az= ‚™ðýÁiÚDýžë]fD´Ú_—9ÇÚ¦»÷÷£ ¥OèaybɧN«ª=ÇIX{3îdè> 'yç&_½!©ÕV+©{´­IÑù–Ú&9¥Î ê™Ó»ǵ%ºêä9 sƒ­¶™6SÔLøƒçM5¡}o]Ö¨ËÓý»:"E^gÆ æò‡â|ŽÉ˜ÔÙSi;̵øR“~àãiõ€ýسU©ú7ÎìˆÁ1‡ÔccJ$µê9³Z­V«Õˆ¦PZœ€v4ÒåÓ¥ºwtCÔ²´µ1…ô0ÖœW ¼Q¿Þ¥†òë+Í9oî/¶*Á¬Åu‹G­ÌŠ‚UK^wUm®3‚h5>X# ý3ý½ÔzŽYU&Çõ]’]±XCP‚öX£0Ý «R»´ÌÚ„`ÕÁ×­U7ªó§q– ©}ïT“°œåa[3<×çÞ]u•Ü¿'ý*ÌPþ£ŽË ÒË1¯ÏªÑS@R'µÛPì t@•Aà€…ÿ(å,bíßê®h¹ìMg‰ÑÌ­d$U* Ñ¿FÓJ_\7Jô×ÿX¦qc£¢¶ö´òL£kæÑUK–T×Å A¯¤eãì™ý½‹J©ƒÛ×”Rû—”R{¯^ŸíR1vÏŸ6Å“'þè)$ƒÇS 1€T¥ ©H*’ H*’ €¤ ©€¤¿ž's執á›ç¾> «T+/ýÁïXûå¿ÿGºTˆä¹©wÿÊw]omm²aH*Œ“õ I$F+©Ë ÿo¼ÿ㸒Ÿ{ê×3™€¤Âˆ£ÔçŸþ¶Å¾~ó?£ƒM¢T@RaÊ’§qH* ©0Ã…ÿ"É5©îêr#’ s÷³û’kßyìßo}¨¿h¾QrÄï<¦”jk ’ ½Hê½ûÂkßyì½nÉï•”qã{Ü:ñäŽn̉'wÞûàVtuH* ©•x½óÞÌ×Ožx¢~±¾ “¤VUœ<ñDeÌÉO¼óÞLKt;­æÄKê…ÝKø«ìÂkn¼ûÁ©gž=¥ó 4=í;J=¸>ßpŽ2 Ô%O¤ŽRóÅÃìÀ|v6ΞÙÁ¿Iø?Töš}jè]]IEND®B`‚jsamp-jsamp-1.3.7/src/site/resources/images/bridge1post.png000066400000000000000000000305371356450004300237170ustar00rootroot00000000000000‰PNG  IHDRÉTžóÆbKGDÿÿÿ ½§“ pHYs  šœtIMEÙ 3Î+¢tEXtCommentCreated with The GIMPïd%n IDATxÚíw|Õúÿß³}³Ù”MBz!!”BQzWˆDET¬(Vl(úÓ‹~ÁŠ×ÞPéE,®J”"½7 5Òû&d“Ý9¿?Bi@PÊy¿^ˋ̜9sæÌùÌ9Ï™óÌ£ÄÅÅÑ·o_‘˜˜Htt4ɵ̡C‡X¶l™²páBvïÞÇ /¼ Z´hÃá@UUYK’kEQ0™LìÚµKy÷ÝwÑ%&&Ò¸qcŠŠŠdíH$§(**"66V$&&*Ê?þ(üýýe­H$U••…. —ËUcB!eee䤥±uþBÒvïÆQP€ÑË‹ øxZ÷ï‡-(½^¢(µžüÌ # Øô¬ß)Húß4šÅãZmži©Ìùe*þÑš÷ ¦A€…2c>ÅÉ—ÚQ=ŠidÅh aÏÊ4æü2•fÑñ„…ÖþœÍÞÄO½7<Á—¯·#Ï|^\–‡ÃåBëNUÊÆŸÞfùZXîÙŽŽÏtÃpj³(ƒ¹Ë—˹®w'zF™ÝGY»ˆ¹Ë×ÂõíxàÖòüŠw/d¹2„ÇÚz³wé$¾Y8—oÖ>ɯöÅóì²'±vyw?y+.waœäm[ÈróX¥2VÛcií,¦Z‡Þ×"µŠdßâ¥ø AOOÂx`Þµ TÇž=(öB ,ò íì[¼Ûð»«Í3e坸Y=;†`ô6¡·jйÒp[ Š­óFo ¾+==uäü–LÊÊ_ñ¹ãþjó\þç"´FÁõÿŠÄêkBod©GÙ–¹’2Õ]›‰ÙWO ÅY5ù Ëÿ\İ÷ÕZI%§ì¯/^¹—溓h›¼ÄŽ¡%¤Ï¡¬"Qþ~XrêÿK–°çÞδð.ÿSÕ›N—sÃAº…7;õWœ:ÈjBërá ´½évîî‘ûYðr^]¸ö›ˆ1Ÿ3a XÑ«.NßJƒ°OåYûpK T—ËU.­¶âºFDRÛ{Ýáôôð \£ÁOQð@ ÏÍA³qú’Œ€A«AQ-=—^‹ÆjÂhóÄàcFëa@cÔ^sjH­¢8œ(]ùðËZ€ëT£©½N‹ÉdÄÓdB¯Õ`Ðj1ëô˜´:‚—““eN.N­ŠÙh@§ÕÕI$.‡OnXDÜäù_~ÉÓó¿dð¨÷Ö5¢\ I›™ ¼v{<©©Nú¿6˜Y¯}Ëæ¤›èí‰Ë%€"¼¢Ú1 øbÝnŒögÍ´ƒÜÿU <ÿVþÔ?%6½û&¯6?Ì®]=øà¿·aqäQrvÙNu_ÿø3½ ° °Àr:O8-P îýŽBàþOØ<²5¹ù%4~i,~¹›•»Sé)ERãS48˜¢ì,ô>>h,o/ðñ«EQEE(yyh ÐÛí9l~56¾RO?ì*è¼Ìh=ÍhÌz“ö´HÊT´:h´èT(T¡ÌÓ] yÙ‚9™WŠIgÀ¤Óá¡×á©7aÖ™A‘³ÆÎY†Ãéäd~¶àºª®Ž1Ñéî³zÀm|óïû™úѳø…M£W„†Ý«&0ýƒ÷ˆöØ-`âŸÑ=²ª€‡êMÏ'¯gö'ëØÜ$€tgv¤–ü}v§´Ã ̈^‘xXÃiBYöq²í®*¦¾‹€î̘6ÆFN ,Ý•ÿ³ŸÎSB=-aqº÷ÀõÞZI9õ-h †kÞ˜¯Õ&ñJHàøÊ”ùxcòñ__°ÙP¼¼NsŠÀhD1(ÓéIIKÇÚ¢yc}mX ’37ÑÆ¨Gc6 ˜t`Рè5 „FŠ Âá"9ÓŽ&üºóŒ‹jͦCËк´x˜ Xô&< &ÌZP B(€‚³Dv(v1½Îc­Z1iÇ£5òธÚgi9¨^™ü÷èþÈúGzb/ub¸¹-çaâ¼ßÙß/˜S6IYQQÀ'ÿæ­· Ñ#Ÿ`Í)&ûÔ³]¨*ê)ûຘxšÅ„¡:KÈ8rg5o³ÔS¶GÎáƒÒWl-£PTÎSè4§®×EfÚïà}]¥ýB#NׇZnYtê5¿ž¯ÖžÄ¿K'òwîdƒªÒÓ×­ÍV.‹¥|¸¥×ƒ*°1'üðï܉¼“'«·IÚÜLÊ¢ÍlÛ—Fû®A¯E£× ¢((: ªPÀ;ö¥QT !­o&ïdiµy¶oÒÝÉkØ¿)•Ž=b1hôè5´Ð+z zTþÚp³ÎDû¦=(.,©µ’ÒwüÆ6{(×5ÃÛË’ ‹hÞ¨G6ÏåÝsKo"µ.4Š‚P­mÏ2ñ÷÷Y¿ã¢‰òër‘cmÆÃÀWÀÐÎáä»ì!ÜÃ-¥â©_œOF¦¡Ö²•÷RPvÖìVy«¯È³G>lÞ±›c1ÿí#>þèvúœ: l~:¾v'­# ìüåKþ ïwˆÄUj—"©ñùér> ?GW¯b³*håáÁdB å"QUÊÌlÙ$aÿêKqÅ4bµy‚Oë;ؽçGL6 ×E ¸‡Šû ¹sÏ öìMŧÕ»¨1O§C¥_ûá¬Ü=‹>i4oS£¢åÏI—*p:T’¶¦râp.ý;Þ‡Ó¡Öi(¡+KgÆç“™qƶoN¥›oßþº‚F#>"(÷8iegØH¶öŒh_o:Ä-±ž€—pá*p1pÞLÚeëð´“WèBU-ॢº\¸Ê'׺•MÕ–Õ¥²HÊ7kOåéAû‡GðÙ ŸóÂ2 ÷ã¼8bïl>}N‡hdà÷מæãS¹<óå\šPH–ëÚîI”)S¦Ô¸,E«Õb³Ùp%'“·m;!Q‘„úùáuª')(.æDv6©G“ñjÑmD9995ÞàŠ<‹ï¢àÈ¢šøÛßòÉ€üÜbŽÌäð_¹x†÷Ã#¬yóì(3ão‰¡S|"…yEuÎótæzLzŠNÇIÊ®À‡«F«G§…ÒÒ²*öiÁåBE‹Á E8K¯Èk¼$"ùúë¯E*X£Ád2áïïF£‡ÃA^^YYY”””œ—‘w¥ä)¹ÆE2qâDq>hµZ4{õ¬å3"3Mx¥ä)‘SÀuâR<¯”<%×hOÒ£G!«A"©¡' “µ ‘Ô$’–-[rÏ=÷ÈšHª`ÆŒè*þ ”5"¹fHOO¯sZ¬.‰¤œ¼¼<Ö®]+E"‘œªª$''³råJvîÜy®M"«Hr-SPPÀîݻٻw/¥¥U/ •"‘\ÓìØ±ƒ={öÔø^M·$×4-[¶$!!ƒÁ E"‘T…Õj¥}ûöôîÝ›àà`4Í?3Ür:h4š* ‘üÓ(ŠBxx8^^^ìÝ»÷ÒˆDÁÂ… Ù²e iiiDEEѨQ#n¼ñF¼½½éÝ»7Ý»wçµ×^`áÂ…Ó¦My‡$— ÞÞÞtèСþ‡[ÙÙÙôîÝ›G}”’’š7oNZZÿþ÷¿Ù²eK•ÇÌœ9“?þø£^.ìøñã=zTÞaÉ%ã¢{’—_~™C‡±eËÎéaªâÛo¿­· xì±Çˆå?ÿù¼›’KÂEõ$¹¹¹|óÍ7¼ôÒKç¤b¬WÇçÓO?uÿ™™É­·ÞŠÍf£Y³f¬X±¢RÚO>ù„ñãÇIçÎÙ°ao¾ù&¿ýöS¦L¡I“&ÌŸ?€µk×’˜˜ˆÍf#22’ŒŒ y§%ÿŒHöï/ÿLhûöíÏë¸cÇŽ‘•uÚ'|ذaØl6Ž9ÂSO=ŨQ£*¥}ë­·ÈÈÈà—_~!((ˆ·ß~€§žzŠÎ;3tèPV¯^Mbb"£G¦_¿~dggóûï¿ããã#ï´¤JÖ­[G~~þÅ‹DQ”s~=€Íf»àBž âH¿~ýHII¡}ûöìÙ³‡´´4wš=zðñÇOß¾}Ù´©ü;ÄžžžFÌf3nW]Nǯ¿þJrr2QQQ•æÀ«ºù»:uaÇŽ¬\¹’cÇŽUkÔÙ&©*ƒ¨¨(Ž9Â…ú¥TÝï¾û®{ЏC‡8p€   7nìNo4«½˜ ¦OŸÎˆ#hÔ¨Ï>û,¯¿þ:&“©F;IruQW‘¨ªJjj*K—.%..Žøøx¬Vkýî±±±øùù1}útºtérAyDD”O÷µ×^s—.–ÈÈH–,YÂüÁ!CˆˆˆàñÇ—-GR-¥¥¥ìر§ÓyN[¾(›Äh42~üx&OžÌûï¿ïþÈ‚ªª,X°€ýû÷ךGLL 7Þx#o¿ý¶»W)**¢°°°NehÔ¨[·n­´mÕªU8NºvíJtt´ôw—ÔŠÁ` E‹$$$Ô¯á0räH¾úê+Þ|óM¬V+M›6%22’‰'Ö9™3gb6›‰‰‰¡Q£FÄÄÄœÓð«ãàøñãDFF²téR|ðA4h@LL <ôÐC²Hª–Ñ«W/Ú·oWÅ7®ÏL3aÂqÏ=÷Ô虨(JÆòǧ°°¦M›^PÀN»ÝNQQÑyIž8q‚   ·]SÑ=¾”\;¿¦6[ᙸvíZââ⪭ä¾[\ìG%<==ñôô¼ cCΊÑ(Å!© :uª5\q(‘H‘H$R$‰‰D"E"‘H‘H$W.ç=|ç„M²Ö$—-ߎ¾þŸ @f¶þ¼Ò¦ï#cßRŠsŽ`ö ÿQO¼Cš£häW$õC€_ÙåÑ“œªÓÁÎÿ=MYËãmÀé‚E¿ ^x}Šošx ­Á"ï°äÚ³IòŽmfÓÔÁ¼ùØ*þÚ$¸¹·B… …†kHÝ #úýÁöÆUvò­„á½Ò5@¶Éß'’‚;Iúå9æO:Áˆ{5PZ“…òøâe ×+¼9VØûw³ã¿OŸÚqþü÷Õx¾})®Ò¶o fÙÛ-¹¾qÝ–¦ ìèOb›l ’¿G$¥Å¹ì]<Žÿ~O÷. ¨å wïÌ[ ²ç/:p)¼ð”†ö±›ÈOÙ!ï†äÚIîÑ <0ð½zŸˆ~Z¤Òî&•Á÷ ⻪ øÌàž WÉOþó’]ä7Ï4áÅÛ#ÜöD,cïŽ:'ÝÍ×Û˜õb3f¾ØŒNqÞ²uH.á~2}3cÞÊN-•7Ãÿ{CP|†Ù1ãGhÙ\å¹Ç4 §Q¯¡gÂi›—þüÅ]ª2ú›ƒ””ª˜ô 7´ò%*ÐÄ‘ôÙJ¤Hêyü¦=«–AǶðûY„Îüˆ·*À©^xQríNùx%ÃýΞ Îo²¡ÈEIi¹›ïÑ €‰äÒØ$˜Ã)*÷x¦ž{´òàF£×žWܽM¡J5Q—öi -/ƒ·E‡A_óe‡ù—šh÷Ñ"ÙB$õ/£_+¦èN)@…¾½¾úBÇë`àͰò ­ÚœŽ^ðùd0u¼dy,ËAà 37]gãÿ†G¡«âªƒlnºÎF‹†Ú5±r,ÓAf~™l!’ú‰wh+Þ˜ÊÞ]Âíc¬Õ*$.ÕÅ·³¿cö¤E ÛÆ®=‚Äa]òÄ =Ç—áðáôlÙ(x”@ii)'Nœ^Ùëçç‡ÅRþ½¯fÍš°k×.÷ñ;w®”¯ªªŒ3†¸¸8¢¢¢Ø½{·Œä+m’ºsG¯Îx+^ä¥er²H¡[#3c^‚}mÅ×Û„W¸G!ªaC\ªJ™«”Ý»v1쮡4jÔ€Àfeü+‹%³Wñã§ó˜÷ÞwçuþŠ€¦ÁÁÁ8p ’p4 Tí­XµS§Nî°Ø›7o®”fΜ9¼óÎ;Üwß}о}ûË!‘"©Dß»ï WÕÓ¶«V#¼ðjw~_²žÃGҲļë(EEElÞ¾œÜLæÿò Bq±ÿÐ~üv;ýÛzT³®6Z·nMFF«W¯æäÉ“$&&âííͼyóعs'sæÌáèÑ£ôïߣÑXI8S§NeÕªU,Z´ˆ&Mšî>~îܹL:•ãÇóûï¿c·Ûòu?ÿü3û÷ï§  €âââ*Ë!‘†û9|ðíûbår‹p”YÅW_G Õ©ŠÏ¾øXDG5:^<;þe±}Çvñö¤DxhCQRZ* OˆVݼ/jì¹zõjѤIˆ¯¾úJ!Ä’%KDDD„„¢(¢OŸ>âĉ• ÷ÐÐPѶm[1þ|÷þ%K–ˆÈÈHAùàPtéÒEää䈖-[ @´mÛVŒ1Bâž{î©¶’«Ã&©·è»f…“Îìê^þÚçÍ“°zõÊ\e˜Œ&‚½=Q4¥œÈ-¥¬ÌIžóÍB¯#3;£^Äž‘‘A@@@¥¨¿éééxyya6›+¥mРÁÁÁlß¾ŒŒ üüüÐjµUæi6›+)ÍÎÎÆÏÏ€ÌÌL¼½½1œñU‹ªÊ!ùû©kôÝÚ˜1cFý9]tñçŸ0¤‡žÖM³yîñX„ÖAiY BºÞò/my!¾¡ètZbZÖ›@*þÙ 300ðTu\U©Øwvß THuå\ÙÔë²”Ž;°èL ì…¼üI$SìÊ%›yþ•ˆ‹mÁç‹?å‡ÓÈËË#¿0ç»èiÓ¦a2™äÝ—üý"©ÀËÓÊ_ëÏÁØÿ÷òeqÑ}úô‘w^ò÷ÎnI$²'¹@ÔÂBŽþµ²Ò2ÊÒÓÓ1 X}m´èÞ FjTr‹Dcµ²tÅ[--¢ß¨lìy:Þ™ªáëîE£é!k_"‡[Óqc§ÍÌü×pR¬z|ÌÛ¸í†õ,Þ<‰¯·¥ÖË9^ýuÞÿýó:fåÊ•üùgõ¡Þ~ûmÞzë-Ù:$ÔÛ{’3yà,z…[08²ùlõ¬0ßÀ¶OM¤mOµ„Žª á¬Cì½#‹^{Ápæ;ú:&&&UUÝë¸$W`þß“TÐkî¾éêϳ«SiZ¼”%ÜÅm÷èAoÀ`T#{3ítñ «D¥×‚οoÝÓÖ­[Y´h‘lI’f¸Õ½Q Ï/ÛOÚ=Ñ4sî 3Iƒ‚õû ¬§•å3nÂ^â ÊÇÄÒ”‹ qPU…ƒÕäÉ“‰ˆˆ`ûö«€ð·IDATíÜ~ûí<úè£î4›7o¦uëÖøúú2räHG¥¼7lØàÞÿè£2pà@zõêåÞ¿{÷nzôèÕj¥eË–5å$R$¼Ò“÷{7@¤¯"7Ù€ AÞIÐY„;¶²¯|åm|€'ßtñ#1Ì|Áç|8‡f„ èõz.\ÈÁƒ(**"11‘ÌÌLæÌ™ƒF£aèС¨ª*[×UB½Ïn¹$C—¿à†kÄk0*>œ2e¯oãNkÐ^¸^===Y¶l‹…ââbfÏžÍîÝ»‰@¯×³fÍ÷2ø3IIIaÏž=ÜsÏ=<þøã,[¶ÌÝÈ>Ì_ýÅ}÷Ýçvã]¸p¡{ÿÂ… 9qâ#Gޤ}ûöôë×7Þxƒ]»v‘ [˜ìIj• YI:~[PÆ¡TµÜ&9EÞ²ßêí,Õ9PUR¥@~û­¼­Zµªv& ¨¶Á=z€qãÆÆ{gÑh$##C¶.)’ºQ˜®’™¯²í‚Áët(ÅœýI—ä|g:PÕ… '­ääd ÜqêL ÑU8qååå‘——çÞÀ¨Q£())qÿδY$r¸U‚’“:œ¸((¥’á®{þJâ«é³Xºv#‘1øå› ûþV…ULL ‹-¢iÓ¦„‡‡×éØ.]º`2™˜3gñññ,]º”¬¬,BCChÛ¶->>>L:­V˺uë(--u¯¸é¦›ðóócòäÉ´jÕŠN:±}ûvâã㉕-Lö$uI±“2 JD¥žDÁÅØIß“Rª%îúø…\ðYš5kÆ'Ÿ|B·nÝ(++c„ u>Öd21fÌ233yòÉ'IHH¨$0­VË矎ŸŸ ,`Ô¨QX,÷|›ÍƼyóhРwß}7ÑÑÑ<òÈ#;vL¶.Ù“T# !ÈÍÍeÓ®¿HJJô8…“‚ÐXD%} \eNœe¥d»°wgŽýÓÓÓñ÷÷¯äR•mpö¶±cÇòÜsÏ¡Ñhðððà•W^©´¿C‡:tNǪU«HOO§k×®îý]»veïÞ½äææ¢(Ê»"K®‘´þ×D7‰Ãh4òµë5‚ è’(óH§À™KŽI%ÍæORËÕByp8 ÍãêåÜ5­¨Ë Yu 4ˆƒÂþýûiÔ¨ãÇ?'¯¯¯lQR$µÓ8>Ám+(œhЇÆ>V>>Á‰4½ÂÆê+V¬`Û¶m8p€Î;'[ŽÉ…óý»¯\u•äëëKÏž=éÙ³§l1Òp—H$—¼'9UUIÏÈ@9˸ ”NW’kW$BEáûï -=ÇÒñ´zâa2Pr²”üìlš5iHP` ƒoìN/‘\3"Q…Ï>ýŒÌÜ:÷èÉ-wÅããcŪ"' ìlß¼…ß—ýÆñãÇyjÔSÿh%¼þúëX­Vž}öÙü†¬\¹£ÑHÇŽÿ–ómݺ•Ö­[_6 r×®]4iÒä² †tIÆ;?ýôÙEtïH§®0xx ×@R¶­ÞޞķºŽÁwÜyÑiРÁ9o×_~ùeEañâÅuÊã³Ï>cÚ´i5žCQî¾ûîJÛ'L˜€¢(´iÓ¦ÞêîöÛogäÈ‘•¶••]š0ÞóæÍãé§Ÿ¾¬žÚ{÷îeРA•âÉ\u=IVVk×oaðС4kÑŒü“N’bÍÆmèt:¬fâ›4&6ÌFë–M®¨¡Öüùóq8îo Ï™3ç’<ÙÏ´Õ.U­ pÇw°bÅŠËªŽ‡ ‡~È€X²dÉÕÙ“|;ç;‚Chܤ1'K?‘ʲ•k˜üå—¼8|¯ËïK~%Àêù· ¤yóæÜ{ï½î¿ÛµkÇ!CÎIWã@‹-(((p߸¤¤$öïßOhhh%7ѺFÙš7oMš4¡]»vlܸ±RORás±‘¼Ît4;›qãÆ‘˜˜è1Q[ô¯ªò¬©7n¤Y³f<ðÀ¤¥¥Ñ²eK–,YÂÓO?M³fÍØ»w¯;ÿ^xgžyEQ?~|ð‡¦oß¾•ö×5ÊVXX˜ÛÅb±œWÔõ59šUœïì÷Tçã¼V—r„……ñðÓ››Kï޽ݳh‹…””ĦM›øâ‹/xꩧΙ!½\"‰Õ»HzߨÝÛ¶rxÿ!ŽK£ãu­¹u@_’’þr_ü?AÅ QVVV­3'Õ9nõïß½^ÏØ±céÛ·/•ö_H”­ºp±‘¼Î&22ÒmÛœoœO9rrr˜9s&f³™eË–±cÇwÚ;ï¼€'žx‚ŒŒ î¹ç÷¾ììlrss‰ŠŠº:E2ôöÛ(ÌÎbëÆõ$:Ê–?×òʨGj p)iÒ¤ ;vì`êÔ© 8°Ê)Õ³#_Uå¸åííÍ 7Ü€Ãáà¶Ûn;'ºFÙ:.4’WMÓºuëJ†sUuPý«*êRŽgžy†ÐÐP-Z„ªª<üðÃîoÜ|óÍøúú²~ýzFŒQ©}T”«_¿~W§á.„ÿ;W¼þo‹7ÞþÈ=^½Tˆ°°°JÛ^zé¥J6Éĉ…Õj^^^búôéUÚ$íÚµmÚ´©2òU…M"„7nŸ~ú©(**BˆJ6I]£lisDGG‹èèèj÷_h$¯3ó¨ŠY³f N'vìØQ§è_UåYS9-Z$ôz½Ø¶m›Bˆ'Ÿ|Râ³Ï>sÿÐC N'’““+åÛ§OѶmÛ‹j7—µá^Á„ ï]rœv»]”––Öš.--Ímx^ iiiõ:‹—žž~N]^ì9>üðC‘˜˜xŽÒÓÓÏ«.´={ö<Ç8_´h‘hÕª•ÈÎξlÂÁ]²µ[Ï?ÿÜ?jƒœM…AZ—I€úšL¨Oª²7.ö£Fªr8u¾öÓ…”cÊ”)¬X±‚Õ«WWÚn2™X¾|96›íê}ã~9 CR;ƒþþè_ÑÑÑ,X°Àý2³‚îÝ»_vu¤“ÍDô¯ËQ Ûì–Drµñ·‹äàჲÖ%R$ÕÚ*mnÄãË—5/‘"©’HÀŸ÷úܽé½÷þÙYŒ ‰”U¶nÝzYÝè]»v]2¿)’úD”ÿròrHIIfõj…V­œÿhÔæpu6uihÒ™IФN¼«¼Ë8e¯*¯–ë£b±Z0øúøÜŒÐÐNôêUpÅTVçÎk]OTáÌôæ›o^Ve2d¹¹¹ 0@¶úËE$V¬¨¨èÐ1Z]þîä#Æ”ŸXSDÆõãT³víZ àvþ™9sf¥†]“3ÑÙŒ9’„„l6ƒfçÎ@õPg#™®B.Õ²!„˜5KÌg¾Ïx‘|¤|}Îì‘#E¦ÕZ¯KN®»î:Ѹqc±fÍñý÷ß‹5kÖœ—3Ñ™k’{ì11wî\±xñba6›Å°aêu€:›kÁ™éJ+aí–B¨Š*~ÿâw¡ªª€Ñ^©÷ÊPUU„„„ƒÁ ¦M›&\.×9 ks&ªj1`zzºhÕª• ©ÖêZtfºEré ÷»@¹S¡Í]mxçßwñõ}¾þ§•…/¿üî½÷^:tèpÎÌR]ª üCzcÆŒ!..ލ¨(vïÞ}^Ž?Ò™IÚ$u&-5 nû—v<­ž¼ürw¬V;99Þ—ä"ú÷ïϾ}ûxå•Wظqc¥À¢gS“3Ñœ9sxçw¸ï¾û((( }ûöç¤9ÓêœnéÌ$ERW‚‚ƒ` xZËÃÄ>x”‚ÏKrEEELž<™‚‚žyæ|}}Ï £PWg"»Ý”‡aøùçŸÙ¿?ç:@UÕÀ¥3“4Ü/;rrrDDD„PEhµZÑ®];±~ýúsl‚º8åä䈖-[ @´mÛVŒ1Bî/¤Tåu­93IÃý IÅÅÅ"''§JÏÅóu&ÊÊÊrÿ?##C8ŽZ ®g¦kQ$WÍRy³Ù\«}]‰üüüÜÿ8ï|¤3ÓÕÅUïOòw;U ™¤H®þng¢úüR WÈì–D"E"‘H‘H$)‰(,,dæÌ™´nÝš™3gRXX(E"‘œÉO?ýÄK/½DDDË—/¯Ò@~RHrMóÑG1tØ0{FFFº‰Dr&ãÆC«Õs×w¢( Ç—"‘HΤ[·näååQZZJfff•nR$’k<<<ÈËË«6,¶4Ü%’Z"‘H¤H$)‰DŠD"‘"‘H¤H$)‰DŠD"‘"‘H$R$‰‰D"E"‘H‘H$R$‰‰D"E"‘H‘H$)‰DŠD"‘"‘H¤H$)‰DŠD"‘"‘H¤H$‰‰D"E"‘H‘H$R$‰‰D"E"‘H‘H$R$‰‰D"‘"‘H¤H$)‰DŠD"‘"‘H¤H$)‰DŠD"‘H‘H$R$‰‰D"E"‘H‘H$R$‰‰D"E"‘H¤H$)‰DŠD"‘"‘H¤H$)‰DŠD"‘"‘H¤H$‰‰D"E"‘H‘H$R$‰‰D"E"‘H‘H$R$‰DŠD"‘"‘H¤H$)‰DŠD"‘"‘H¤H$)‰D"E"‘H‘H$R$‰‰D"E"‘H‘H$R$‰‰D"E"‘H¤H$)‰DŠD"‘"‘H¤H$)‰DŠD"‘"‘H$R$‰‰D"E"‘H‘H$R$‰‰D"E"‘H‘H$)‰DŠD"‘"‘H.9ºº&TEÖ–DФ:„²¦$r¸%‘H¤H$)‰DŠD"‘"‘H¤H$’+ÀŒ3dMH$Õ‰Äf³ÉZHjàÿ.6êÅ WIEND®B`‚jsamp-jsamp-1.3.7/src/site/resources/images/bridge1pre.png000066400000000000000000000211121356450004300235050ustar00rootroot00000000000000‰PNG  IHDRÉTžóÆbKGDùC» pHYs  šœtIMEÙ %Á±^tEXtCommentCreated with The GIMPïd%n IDATxÚíÝwxTUþÇñ÷;5“:I =„ÞQ:‚¬€‹}WŲ6VT×ݵîêâ×ºê »«¨H·¬Š«EŠ€!4©!„TBÚ¤Lfæžß) „µð}=Ïð03wΜ{ç|î=çæ è9a„nW^yå/9Ž„¸ˆUTTd9rdÍ’%KRÿñlׯÏ-·Ü2Þn·w÷x<†!KI\Ô4MÃn·SYY¹í?ÿùÏmÞ¼ys¢¢¢nõù|²t„8‰Ùl&77÷=sDDÄ ªª*Y"Bœ¢ªª —Ë5Ȭ”Šoª‹¥”Âëõr<'‡­ó’³s'ž’lÁÁDuêDÑ£pEEa±XÐ4­É œ\æÎùŸ“·3 OqM™!!´èÔ™N£¯>§2sósXôÕ|öÚIIY1J)‚œÁtlÝ™‘ƒ®¦edóË7]×ã´eË–©¦žÇã!-u9gÌ"Ðf#²E$±±(¥(8z”¼¼|ʪªè}çt¹âØl¶F`m™ß­He÷û3 °YiEhlhEG—My¥‡·ü†vC®hv™+Ö/cî¢Ø-DÄFÐ"²%~¿ŸÜ¼l ²Žã-÷s먻Òwh“e^<üdíÛE™3‰61A²8N Êm·Ý6Q)EC¯×ËŽå+Øôö4ú&&0ìê«ùØ£ô¾j81ž*òóð”¹Ù³~#—‹ˆÄL&S£eî[™Êþ¹SIŽ´3¤o+Þ0‚L ûˆa´rù‰2 ðqpý×ì"4!±É2WnHeæçS ‰³Ó}P27Žºž‡nþCú ÆâªÄpœ·›õ߬',0œ„èÆË<ùql÷—¼øä8þ6y³fÍb¿–L¿.q˜Nš¦to*¯NþOL7’\–º×=¹ßðÖ”Y,K+£ý%­qœô_aSÿñ/ÖuÒ£c4&Uκ9ÿbîòõlZ¿žõë·r¸¸Šˆ¸8L§×ëèºùûÜ´éÕ‘Àº÷=|óáfm5èuJÏüð°òæ;y:j·µw5ky\L³ßïo4E99l›=—¶z;Äû \YY{÷¢ ?®ì,ºaN6*ŶÙs‰éԑȘ˜Ë,ÌÉ&ó¿3éšä¤O—‚£l8ôTI˜L˜Ši•`Åe‰dCšbßgÙ¡á1± –™“—Í‹fÑÚIç!Ñ´ˆtⵓWžAy•# Œ˜” l®v­ÌáƒE3èк1Q±M¯g 6qË„¿Ã/Æóæ3½)Þ5ŸÇ–áñûÑOô`ùæ³H]©½é÷ð ¬5ïøÊò˜—š ¤rɰþ IrÔ}*}Ýb楮ƒK{s÷ Õå•ï\Hªv#ô a÷Òi¼³pï¬û=5’ÀSëV¾u©eÜö»ð×UÆGÑ· I¥-Ô«cƒÛaœ½¡·ÓNSíá¢À7µPö|¹”0¥HŠàÒ;v``€¡ص Í]ŠU«ÓIq©›=_.ÅuÇm –ytå„;CúÅ` ±c 2aöçà;²4º/ [ˆ‰0SCÍ_žÁÑ•_zÓ] –™úõbt›âÒ_&fÇbS3ómþJ¼†·ž#ÌBKg¿´±úݤ~½˜1WßÙäBª¬½ñ—ßÐÙ\Þî ¶ß\Ivæq¼µoç£%5ÿ_²„]¿@—꧆Å~¢ž0(¾Cͳãl\Pó¡ ;ºß?hÐëÊ_s×5­1ßw žÈS ·‘å¾’dÇi;, ,†Ÿ?¥5pÛjÊlº»e(P††ß﯊®7#\IH𴛢[@ñ&ášF KáqLßlÀRY‰ °ê&4CÑ- €£‡5ú÷– c»iÕÉ…£E¶P– f‡“÷  ¡ù0›˜tL63=:¹H/ØÓh™E{iÝ+’–qa˜­:fÝ„¦W’ïÛ‡2f‡Ÿ@«¿ß†#ÀN«K#ÉÈÙÛ¬¿ izõîñíë¶Û5(" 81œKßð%ûÂSOµfÒ¤i,ßpNC“ª×ÓFõtmÛÂæé8zu;¢u¨ÊÜÆôý0d¬(Uø Sm™eÇÉζ’º …ì&ƒS««jþÕ,'¿gB©ê׫ˬ`Ý¿žeWçû¹wpÙëÞáù­mxñÁÁ8j¿³ê0+f¾Æ+¥IÜ9ñ ®ï-!ijKâ,.!Êb&Ìn#H×±šÍè3èæê•˜ÍŠÙçC÷ù0|~¢4ÅÅ%n¶]–2ZÄE` €ÅiÃd7cÒu |˜=Vt‡Íb¦E\Åîc–é6JèÔº%XÌ&̺ ³©ú ¯¡ã3 |~ŸÕ ºµ‹ÝY9Íê^Xã{s+ðÆ_îcõèqÜ{í]Ö“¦Ègåä5pë\uU2'Mcîä¯ùÕx"¿a#¹ÿþ¶<üð?øvß5´hȾ ÿ…‘Ïðë^XñžßïÇT»V·èÕkõâ}|úÜ à>B­~ü§ü9Ë0ÀlX=ˆx;ø³ÙÃŽ•À¥µejø ÒØåSuó«|GI?Ð ­fëevÂ7ÿ|†o®ûïÎz˜=<ÏKï#lÊ{ ŠsHHÙ›ulÀ  l6ºÃŽæt‚Ú••hååhVz°k¥è†j´ñù-:¦ ;6W ÖPz€“Í SM—Ú@óøÐ¬æêîWP þšFÓ‹YÇn·h·cÑMXu‡Ù‚]7£PTú}Tx}xü~|ºÃfŬ››¿'”ßm\LÇw'óç7ßä÷óßäú‡^eÌeÕGð¸÷mf0ñ×ÈÎö1zâõÌø>›÷]ɰָý (#8©7c€7Ö§sEëÖÎ<À]ou!0syõZ¿&$f'lzé9žê|ˆ;.çµO~…ÓSDå©u«Ù¼ýñç †2À‰ÆZç‰2BáDHÐ@³Ö½ï)îšÂæq=(,®¤íeã¢ÛX¹3›щ’F×¢ÑєÊÉéD †ÐP BÓ4TYZQ¦’,n7eVWx£¯*0·æ`z “Âf×O„Äk ›Í`Ò1Pj€70s#eF¹¢©(ªÂn¶b7› °˜ ´Øq˜í€¢ÌW‰ÙäÁìóâñù¨(öÒÒݼª¿„Ãévúßö4k®þï<}3^ÿáq3š`bçê©Ìzíï´T¸.`ê×ß18±;†R€Âß]Ê{SÖ³¹]$ Ì{‰:Å{Ü N4h¸þzÆM$ (ž¶mbðdRàöŸa×w0˜Ù3_¦­šN ‡…\Æÿ¹O”©(eœ‰r‚:±õRÀ¥!:ûÓV|¬˜¬Ö‹~0ßä˜$¸kW2W®À‚=4ÂÂÀåB ®éç”͆fµâ5[8š“KP—Îöõõ¸.däo¢§Í‚ÉaE³›ÁjB³˜@S(“ 4&4”ÇOF¾Sü%–Ù1©›.C÷ëØ­8-v­vºhš ¥4@ÃW©È9XDïä¡gq¬Z99G¡Û[rϤי1â!rŽ—`çóÉ"|ßãŒN Ä]åÃzÕPºÍœ©Ÿ®b郞$׌I¼e%$õ»¦<ÍóÏCÊ}S:^NAͺ]FÍøà’äNtHŽÃðU’—~_Í2jÆÇà ¥öU/¥ª~™ Àlª™_?ù9« ä’zï+“:±<ŒêÑ‘Ól\ôÇó5¹%‰ØŸâ´46CÂÂÐ]®ê 8ÕÝ-‹40€oŽBD$úSTQÑð˜¤çU]¼™o÷äÐç²¶`Ñ1YL(M¡išÙ„¡4ðÃö=9”UAL«(ªhøð™>í³3c-{7eÓïò6XM,&+ºÉŒ,š«Éƒï6Âa¶Ó§ýå”—V6¹r·/ç[w,—tŽ#ÄZÈ’_Ð9¥é›çqA<~í0u?&MCŠ®?0uÕ«lØ~-I)ªºÁúýêÀo·€›ÄSìw£”ªëniµkýòbòò­MÖ­z+ÞSönU·úÚ2«ðÃæí;9’Læò×™ü90èÄwš°ùÕY|=ñz$ZI[ô&_‘«}ñW¹%$®?ý~â¯Íá5«Ùl(º`µÛQVkuH ¯#€oU@Ü/GR^»±Á2!´ÇMìÜõ1v—“®—$ Õu´º5dÚ®,víÎ&´ûM”ûi´LŸÇ`TŸ;X¹s.ûCsèÜ=ŸÉ@WÕëI¿¡ðy ömÍ&ëP!£û݉Ïc4«+aöæ2ûßï2û¤×Æ>7ƒAay¼ÿÅ RƾNTa&9Þ“ÆH®>ŒM·7äÚ6€¿òã/ñsͧsè]`&2ÐMQ©ÃpB°á÷ã¯Þ¹4¯n†^=¨¶jõCRý²^Sf}~;–=úo] {ÇÆ®çÅÍ'¾ÓãR¬¬šø{&×”òð›óhG)Çü÷–D›>}z£‡¥èºŽË埑AÑ·ÛˆIJ$6<œàš-IIy9YdÎ ¸Kgô„Ž?Þè\[fyæJÒÔ.ŒÖmZV½3 ¸°œôùú®ÀøQÄunv™Y%‡Øx`ÑQAÄÆ‡â…EeÍ8FvN)}ÛÜHLp«&ˬÛeÓŠh«—²rü~‹ÀäÎ#3×Cl›xìžãìË8vʧ$´‰Çn”‘‘§HˆvwèE^°hS¸ KñÁ±ÉD9*9´ÿ(^,Ħ´ÂQ™ÇþÌ¢&ëÓš¨@È9p’ºY±Óºš›C²ð-h`Pî ‡F™Ï†ÓZQ÷Q ±8m:šæ§¬Ôƒ…™d/¿èwkÓ¦MSMM¤ë:ááá„;ÉX³ÜnhšFyy9Z “ø)(sSPPЬ†W[f¨ÓÎÞõÿÅì=LHPõÒJJÍøÌ‰´é{ Ee•g]fP¨“u»–RP¶“¥²ºÛáuáL¦§á”•5»Ì…[°[Ìhø<x‚+W“nÁ¬CU•÷ ïéà÷c cµê(_ÕOr/HHÞ~ûmÕ¬l2a·Û‰ˆˆ 44›Í€Ç㡨¨ˆcÇŽQYYyVƒ¼ŸJ™â"ÉÔ©SÕÙ|@×uL&SÝѳJUïù>» *e ÙÜ,b üS)S\¤[’Ë/¿\Éb¢‘-I\\œ,! I·nݸýöÛeIq³gÏÆ\û¤eË–²DÄE#77·ÙÓšdq Q­¨¨ˆuëÖIH„8•addd°råJÒÒÒN“È"³’’vîÜÉîÝ»ièúsqQÛ¾};»víjôïjÒݵnݺѵkW¬V«„Dˆ3 ¢OŸ> 6ŒèèhL&ÓÓÝòù|˜L¦3V@ˆš¦iÄÇÇÌîÝ»/LH”R,\¸-[¶““CRR)))\qÅ„„„0lØ0ÌĉX¸p!ÑÑÑôìÙS~!ñ£Bß¾}Ïw«  €aÆqÿý÷SYYIçÎÉÉÉáé§ŸfË–-güÌœ9søê«¯ÎËŒeffrøðaù…Åó½·$O>ù$dË–-DFFž¶…9“÷ßÿ¼ÍÀ<@›6mxå•Wä×Ä÷Ú’òÎ;ïðÄOœÚ¾Þ™ÜqÇüóŸÿ¬{žŸŸÏ 7Ü€Ëå¢C‡¬X±¢Þ´S¦LáÙgŸ%11‘°qãFž{î9–/_ÎôéÓi×®óçÏ`ݺu >—ËEbb"yyyòK‹&${÷V_&´OŸ>gõ¹#GŽpìØ‰sÂÇŒƒËå"== &ðÐCÕ›öùçŸ'//E‹Å /¼À„ 0`7ß|3kÖ¬aøðá<òÈ#Œ5Š‚‚V­ZEhh¨üÒâŒÖ¯_Oqqñ÷‰¦i§=j·.—ëœ+yðàA–-[ƨQ£8zô(}úôa×®]äääÔMsùå—3yòd:uêÄÈ‘#Ù´©ú:ÄØl6‘‘‘u§êšÍf¾øâ 222HJJª·üLó"Ÿç£9¶oßÎÊ•+9räHƒÃƒfIÎT@RRéééœëy)µƒî—^z©nqß¾}Ù¿?QQQ´mÛ¶nz›ÍÖàÌÔš5kcÇŽ%%%…?üá<óÌ3ØíöFÇIâ祹!1 ƒììl–.]JÇŽéÔ©AAAçoàÞ¦MÂÃÙ5k<§2ª¯§;qâĺîÒ÷•˜˜È’%Køê«¯¸ñÆIHHàÁ”–#TUUÅöíÛñù|§µåï5&±Ùl<ûì³¼ûúê«uY0 ƒ °wïÞ&ËHNNæŠ+®à…^¨Ûª”••QZZÚ¬:¤¤¤°uëÖz¯­^½ŸÏÇe—]FëÖ­å|wÑ$«ÕJ—.]èÚµëù¸Œ7Ž·Þz‹çž{Ž   Ú·oObb"S§NmvsæÌÁápœœLJJ ÉÉɧ5ü†Ü}÷Ýdff’˜˜ÈÒ¥K¸çž{hÑ¢ÉÉÉDFFrï½÷J+ vË¢¢¢:t(}úô!¸ö×'OóòË/«Ûo¿½Ñ35MkV_>33“ÒÒRÚ·oN7ìt»Ý”••ÓY’YYYDEEÕkj·D§ö/ÅÅÓøk³µg&®[·ŽŽ;6¸´Þé»çÃ÷½¨D`` çôÙ˜SîÑ(áÍÑ¿ÿ&§‘#…!!BB"„„D ‰?]g½ ø–—7ÉR?Zï?ré€üËYM_𻇼=K)?ž€#,Žˆ”!„ÄtF3ÉUÄùîýqlIΆáóöß?áÒÖñÆ³Šž]Áç‡ÅË>3-l ¯~Ýê”_X\|c’¢#›Ù4ãzž{`5ßmR\5L£E„FL”ÆÝw˜ÈNƒ±£¾bÛG¿Åï­øAÂC[rãe‘ÒÄÿ.$%Yiì[ôGæOËbìoLPU}Ov4ªï/î‹E㹿šxü®lÿä÷5oœ½OžêÄûOt¬÷Ú=WF³ì…n\Ú¶y‡¦\Ó/‚á=]ÒÄÿ&$Uå…ìþrŸ¼]ÌàÕ:îÜ£øtÁ®ï˜A)À¯ñè}Úl¢øèvù5ÄÅ’Âùûšý V>[lÐûJƒëïRtºÌàŽßž|sOן5(Îøú‚Íä;·ã±_'Ô=ÿ×ø6üõ¶¤Ó¦»êRsëÀœÇ:пcˆ´qaî¹›yüo€·æPyüùoŠò“†³?†n þø€ ÄÇhXªvŸówØLÜ|y‹ºç꽟i£¸ÌW÷<6ÜÆ©ò· ³0ªw8›÷»Þ3Œß_˺]ÅÒBÄù‰Y|ÒC‡}OŸ.u5üñÁêét,ªðœ¿Óf11¤ë‰ó\Á–³w•Á#ï ²ÊÀnÑøE÷0’ZÚIÏ­”V"!9Ïý7ý”ê…~½`Õ)7:ù"Þ†ŸqîU)tû¸oòÞz÷[†´8» e~*«ªOó=œçˆ 3&ÁOY9Ôõg*á÷×ïܘL0ñOZÝÖ¦Ô U¦¤ »6Ыëâ4cµ4>ÛqÕ—&Úy¸LZˆ8ÿ!±…wgÖsM 9Tã­W4ú]×\+ÿ«Ñ½gMp,Š¿ ö¨~l&óÐ*ÊÁ•—¸ø¿;’0Ÿa®£\V®¼ÄE—VNz· âH¾‡üb¯´qþCÛ¿Me÷Uwޱ®kÜ{›‰5 u>©sY_x«¯µqƒâµ¹q%õ¹`39oMõÕ"¼:† (÷œ~õ”Œ¼J®íÁk÷¥ ›4¦.Ê’Ö!.ÐÀÝæ$eøË\ÿÛq|úN1í;> Mƒz׆0+v¤)n}(’ö#&a¶ŸÓ÷Ý0içi¯Mû2›i_f×=_¸±€Ôo ñù>¿bé–ÂË 4S\îÇ0ä"vâBI€ –í èñ nˆâ?x<5 ®&$ž*ŇŸ(.».„–¿˜…3¼ÕŸÑÊ*Ÿ¿é†_èöI@Ä…Ý’Ô­‘ã/Áyãlžþx9šJ„c/S9U>ùåm°ÄßLÏ»®Àd¶É¯ ~~!iö!Éá!Ä&\\WïåøzÏdp,~f!¹'µqÑu·Ü•nÞ]2É÷½DûÀö”Y,ÜÚi4™U…¬üv1ûsv±§8Às¬ ñ“‰)ÎÌèøëÆD€Ç–ôM$¶îÁ·‡·Ð©U®ê:”O½Å¼³`^w.Üù…ü ââ ‰^¬q<¬” é›7â^Ö¦9Á ï|3ÿV…ɤ£üŠC¿Íà¶^×òðŒ‡˜q|:…ÛKä×?ÿ(¥Øo0ÏLZA\\(mã_Á¤iÕ»~õê G¶äæwоeFŒAhh(‰‰‰<òÈ#TVVÖkÐ×^{-Ó§O'))‰V­ZñÙgŸÕ½ðàA®¹æ"""êÝßdܸqtíÚ—ËÅõ×_OZZZ£õ?/¿ü²ÊÉÉQ©ÎAÓ¶Ø«ŽVY”»"Hí9¤22]ê/“þªÜÅ%*3=[}<ߥ,«JnÝCµï ~3ön5|ä5*õ“x5ã=—RJ©+þ>H5WZZšŠWaaajÞ¼y*77W•——«„„år¹ÔôéÓÕøñã Æ_÷¹ÈÈH¢úöí«î½÷^e±XTtt´RJ©òòr•””¤ìv»zì±ÇÔ‡~¨Þxã ¥”R<ð€š7ožúòË/•ÃáPcÆŒi°â‡ÕT›ÍÉÉiÖãå—_Vç5$Á±ª¨"HU–‡©‚Ò UäPM›¬44Uâv«ënïª,ºUYt«2›,J)¥ÖnY Öoê§]n>§…ѱcǺ®”R}ô‘Ô¸qã”RJ†¡\.—²Z­ÊçóÕ…$66V¹Ýn¥”RcÆŒQ€Ú±c‡úøãP€3ÉÍÍUÝ»wW111 ÖCü|Br^Ýúj­ÃjQ^NÚ¦/yeWÆNz ³n¡s»N,˜»§î¿áçý÷>ä½i‹Áü-;v)†¹ŒÇ_ÇuºéœëpèÐ!ºuëTßñ¨k×®TUU‘•uâÈÞððpœÎêë}uèЀ;vÔ}~À€õÊ5 ƒÇœŽ;’””ÄÎ;åN¾2&i¾›† D ¦('ŸŠ2A)f<{¾ÛJXˆàø`g¦“Ôª~ÃÀë¯b玌¹õfRRZвƒ—ßcÉ{«ùøŸŸòéß?<«ï¯½¡)@tt4û÷ﯓÉD‹g>[±ö¨ýû÷¯»-öæÍ›ëMóÁðâ‹/rçwRRRBŸ>}­‡Ô3ò¶›(4,ôº,€ <úÔ`V-ÙÀ¡ô¢œÉdì8LYY›·mäxa>ó-Bi~öÜÅÇïoct¯€ïY×”=z——Çš5k¨¨¨`øðá„„„ðé§Ÿ’––Æ|ÀáÇ=z46›­^pf̘ÁêÕ«Y¼x1íÚµ#>>¾îóóæÍcÆŒdff²jÕ*Ün7P}˺Ï?ÿœ½{÷RRRByyùë!dà~š×ÞU­Lu*7H½õv¢2|†úדUë¤VÊl²¨?<û¤Ú¶}›zaÚk*>¶•ª¬ªR¥%ªû ïÕ÷\³fj×®Ô[o½¥”RjÉ’%*!!AJÓ45bÄ•••Uoà«zõꥪæÏŸ_÷þ’%KTbb¢¢ºs¨¨Ž?®ºuë¦Õ«W/5vìX¨Ûo¿½ÁzˆŸÇ˜ä¼Ý}×avRáëŠÛØÍw{BøÝ=‘¬Y³¯ß‹Ýf':$ÍTEVa^¯"_b/!¿ $=//ÈÈÈzwýÍÍÍ%88‡ÃQoÚ-ZͶmÛÈËË#<<]×ÏX¦Ãá¨w“Ò‚‚ÂÃÃÈÏÏ'$$ëIWµ8S=Äÿ^sï¾Û”Ù³gŸ¿“®*|e|ýõkÜx¹…í øãƒmPº‡*o%J).»ö—ÜßíJbÂb1›u’#»·€Ô6üSfË–-O È™>w¦€Ô¾wê]|kY/ ÕCü´×ÃRúõëËâ¯ò)q—òä”Df|<‚K6ó§¿dˆ®cEDPdÙYÐa\ÝQDvg2Ü ã*…U$ÈE.BBBBBr¿§/éÚ?Šmkªª«»ª«ïßïÉ“§»úÔ©ªsÞúÎ{N>M$îÒ‚8‡‹A¹&Ew¥Iø/#$ëTœ$ýl1Vžçµ’~¶Xìa剭nR‚d×,âdÉ«æGÌ”¬½˜ÕQFW#á‚­OàEÙäÂmnDXXjô§Üp'Œ{>Ü“ae›§ð9Q%Àê]ì¨A<:1«ñæúîõøÎ ¯Fú®wáfä™nˆMdž©~A¸ãL\ÔŸO{É%EŸ¥3>Ó£[¼Åç°3"|ib÷bY: c9^÷.„m¾‹ó¹ûø˜G#£b¤U7éË­5âÖè"¡º œMÌŠA¹ n'0 bzF3.ö`) ]7D—î±ÙÙÙ¼Û ƒ^¯'R ÁFjjê©S§°1Bл (ÄwÐét(ÄY­dgëSSSQ.ˆSZÁÆ‘®” ‚Vq’’{I™ÚÍ4̶¬éô[ækÄÉÎN5NÉ –t:W’…‚:óqÅ.V'oÕr%e/qcK‚¼‘ ñ˜b<í]xÁ’K^\UÑ`ýyÖ#$Òƒâ¦;ÛÉ@„À ˆ³èõÙ¬q” â¬VP.ˆ­ \þ®Œ½¯ \8îH ~M ñ»žŽ›åÂíM1ÑÄ4nù!ïüËÑêÈññcMX¡ïîTÒŸ†GX»:1€ù §œ¾õí}ÑÕº\¼ç_÷>¤QªÃUa{OÍþtúœ]OœjßIZ\û·ð+Ö“? Ë`0HŒ.öè ?s¥¾ü¹{žšuûJ€n(o¨ÜsiǧM…ϥν|óê},}$;;›c\’K[o'k‹©M;$T»ä.¨]=Ãdt*+Ž€’Š÷­3» nŠ1Ì\jŒ’"ïéóé‰}g;¾ÐÆYر§6KÜoqª-ÆÈæ]Ú[Éé9dùö—ï<^ðpIç‡(ûBCe¾Ýu:‚$¬×¼ ·zW­î(ÍcŸ={hXìí-Ýmÿv ïÙ©+Ïý¹½hH‚Ñ3¦Ót{„Šq‡w‘Y.&(ÌÑÛfí\Ÿ±þÞ;&õY­g«/®:øÖ½¹S¾ªÿ@©î%ÑDTÅ£wñdŒ‘.—о¯>+]°:_w­¥‰LÖL[yÿæ¿Ï98{B©1YÌùG¶Ïß=³YuBTÝÓXUå]¾¼±1§pÅg§vŽ}4}SæöË+)u„¹+êÙa«¿Xx03õѰPÍÍŽ†E[Ú¤>ŠÔÞåð¥cƒc͸{:ývßéCOndÅá¬Í·¡*0¶ ˜“´vÓÌ-ÐmêY{ O.›‚é]ï"qÜeãc†ÊËïHœûÜøù+¼^ÞPçª/Íÿ8',T­ QÖ´Ôщ§&ßëÊy—”P´¡¡_ØÓ6aw‘(ªwÀ£ñ㯄€ª'iãƒ{÷Uæí?w ­»ÝJY»M= U·rW„̼û‰Œ¡¯‹u¬öÞ2·£>Üê]èñ\–w‘>ª{ž ¦[¯g Ï™}çK-–ë'jþçbí•Ò›0vðÈY)OƘ'öY°üý>®¸*&f3€Yçj‡OÕ2>è>,{ö.RžÂ9/cœ” ÷»Iˆ_+À;Þ…É͆†ÎÎ.«õÖÔ–¨ÈÈØX-Ö zôú‚Êuqƒ‡’!ŠP¥²¶º&Tƒ œ3/ Ë=½‹óç?ÿÐc f<õ›µRA„ÕhnmlúæÐW+W®^·î],÷@Š1ÒŸÆÍùú‡›‘2iBdLLk{Ûõ›M7›ÚZ;;ûi<˜ž>|Dò¶mN=š&‚`ùséš ~õÍwŠOœ©kn‹¬:íÁ†ÆÆšš¯\0K|®dEQEQA%ù绬Y·!!qX¿ßD\¯o.­ªË~fv¡¡àÀÎ]/Ï_Præ|åÖ±ýÇŒ·aÃ×+ÞVCrE‚7í¼ZÎʯ½‹lrQÊÛˆR¥Tì.,ìî5~}úìý“'÷X,_:¤$)+A&$Äwtt¸;ŠÐ·>³blÁ€™€ll1ƒU£¼²²eg² ˜ã‚wéîºY×`µXBŠð°0‚¢ÞÉɹøó…7×¼«T„{Ì£ñÂ… ¶Þµ¨›^TKáL{$K†^ÌÊï½ I5••??I•>}º&2êXñ‘ææÆ?¯^õtæSÚÑǾûABjµZQåË4 ˆ¯yééµï®ÚüŸú~ýúU”EÞ¦í¿Ù ?TTDX­3\E*¯^ª0vv~wìÛÅK–xå‚)Š¢ŠÏ'Æ]âbã´q±õ5ÕíÝqññVSï#=^w£ZEAMKs]íõ!Câ‡âŽÎâgã.*µjÙÒ?^+/§,½µ•ÖÞn%Õ×ÕÖ?(®¦ª¼¥¥á•_^_öŠZ­–ÅØ‹6¯ÃýÔ^»fËÐގΜ s_iY—w¡ÙôÁF…‚ênm(=úäñ#%Ç~wøµµ×I•bû‡[¤m¼en§þaƒÉ›¡ÀAí½æ,‡Y¡wù'^X¸ ­­­±±‘ „‚°˜ÍÑýúÅjñ#z;DGGGGGc£wA‚wy&EeEXÐ=î"›\ȵ䣧°¬É»¸K.}#ú ÊàèÑeG.ü#³wi‚¦_~É"ˆÜ”ƒû.#' ø½wÙCìYC¬YC¬ùxöÇ·úXÓFŽœ—œ,!Àp7b­ĸË€‘ÕpÏ=pÀÓ·¶¿ö¼ðÀ´iÎgÆšŸ†N%àÆ]²VœH§ @™{Í7/ÃÖ­ðõ×°y³¨ü¤)Û?w¡Þxà ÂbVny>|PJVXëÁ0îB½ ÔÛ ‰„Á£`Ìï$w‹¸¢žMšSÍ; ñïb?l†ÄT qÅÞD]‡³Ix'¾à´_ó.zŸÃC`z$H¼‹«³é¬VkkeeS]Édêîîjki 7ujDTº¿Çù×wQ(Uõ?ªoüûæ-¤ÃÆíÆ;‡GïȸŠ%ÞEæß‘Þ]Þ±éFoiÔ€®¯ÎHiZ–ÞñxRÍ“ÿ¨©jjÇGïò+g+jž/®J5YJËßox“¼`‰~ˆz&„[7R«O6Zúœý‚´“c$¢ÖtAd÷.ÒåÒm4?µöƼwõÚ9«)Rs«3£ôÈp¢½ÛB}[×»êøuì+@\qU.ʨ‚ɱ‹ÇĨz+[j¡ËH…„Z‰TþýþCßÿ­vX$YkQŠÊVx¡ÞOí­øboÙŒC^wÙûx¢B¡ ( :®ZC”t«Cj¢ËÊé4 Q¡N(J+Î/”ÂZ[Åùí€3§¼5îb±˜¡¯÷þ Óû‹Õ@·GæÆ&Ém¨íˆßx[GŽíT¼‘×c“‹¥­Íg+ãŠw¼ M{k3˜)…‰ Ã(«µ¡âZeUÕ§N¯Û”ïS]ñ.®Ó™LF0öQ´\BþuŽeK–·Ü>L­ S…*½00ÈcäüŽtÕõk`¶´\”a·îÚR©Ö„QU}­BB·È™»ßÞº/·cçH²w‘]F?”1ìÎ1*•zˆz±ùî Ôù³½`""úºHâÌð¤ÿííš§»sîì§å5¹öÞ 'CËìz\qY.ão}E¤Æ¨mK¾+1yìËgÃ&ÿþ®Yû32]^1JÆæ %âŠw‘m}—]Vù쥢D|Ô» 8î‚`\qÃÚt%?œÿÁóÍûš&ÑÝFŽ‹57ž/nŒl}å¥%o¾ö–{€yéÑ%²ô«Y+FžIŽÚBö…ÕÅ„iû©ú©•sgïõª±Ä/ÆH.V«©Ù\u­ºvDr¾Ó_ù£ÊB€ `ðµGþ4÷/žÜxõÒ5,wô.2ÿãÐèøâÓÍm#’¾¹©(³nøø #&*:C>úùàÅKy+4Kü®¤<³.¼¯>/ç¸Ë¼ ó~;X÷̈w9ù >ÔgêyvÑ»¸ju/Õ7ú½*eTOWÂÈ©4ªa#&Nô¹Õ—Yþ‘Å»ˆŽ.'w}¹üŒ–“BYzä›-GŽ~Ó?*K–ÛIfya¿ð¶Lïb‹1®z—=;öu´¢”P¸»fÚÔ‡e4 Nþ¬w;wê“Øe‚¸=‡sµì3÷¬¼8vàeïÍmþ”A¶‡††Êk-þ¬oJ{éÅ.ÄÔŠ7nïzv½‹ÄžÑömŸd;â¾úãŽøcÿHáwÕéîRvÓWœùÀÀô.Hô‰äñ.HÐŽ» \Àõ.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚ÞAï‚ è]ô.z½ ‚Þ rïB:³'_ÐzVíctADÄ¡è¢Óé°È‚Ö»Ðq…å]ìÊ¥¤¤K-Èã K+Ø!¼ Ê‘î]P.¿w±Å” ":® \Þ傸Él¨D‚7æ ߬H.z½Ë4€M+÷ÑKr|ޏ ÃZ]Ä===ÅÅÅÅÅÅ(ÄV²²²Nœ8ѧ•ÒÒR ±±£¨ÀXýë[–Vôz=Êù5–lݺŽ;Foai%eÙh”K°óÖ_^KÐŒ€­[·ææærÐZ™¸|\þ¬mè]‚š•›^+úäð¹sç@@+–ÍŸµ ­n°Ó^Ö 999çÎ+--µ—lˬé#z¯m‚&ë­ì¸;`LLöØU8sQî»ðm_ní»………‰‰‰999yyy””$°—x‡ååb±z@Ž>Ŷ[ÓF='''11133s÷îÝŠ!ÝwÙ»™t:½‘÷†f}ý€™’••-í­-gÞ̹'ÀJÉ=sî©ÚËu2b•ÇÜ‹7yÝWòÆŽ7ªªª–.] ´bÒÒÒ€©˜ÒÒÒæææØ±ýl;ºË»ð4w;+m£@¹0ÓÐÉl‰yã¼½àV?ë¸ÜSu˜¡½«vSaºBjjj~~~nnnFFÆòåËóòòÊËËÓÒÒ233iC[™ÂÂÂ!Óos{cd¯¾åº?¸µ.W»&6%3™3»H¨ry[É‚‚[s“ŸŸ¼1fܸqz½~ü²; Ïìðï"­ìè½*Æ7¿VÇÛ¤z Ãç›G$^ºtinnî¤I“„Ïü—Y]ÉÂÅà7ë¸õ¡K×,µ ±ØSL|||JJJý€kþÑ3rxç1 f ዲñ (æûᅳvxó·òÈE ¿ *B°ú/¶jà=(37Ú“r3w%ʘ›ð!x/ÜÝcB,ÅÌœ9ÓÞˆììl½^é‚m,Ä]fÅ``Ö/kN‚ º¤×ë-ZôEÅ®=ïùÇ0+ qØ'Ö |^¾sïÚCž¦óŠùEÄj…ù„¨¹¹¹°°pÌ+IÛ³>Î'0©V˜ó½#&ªG;¡”K°ÀÕÊèW†’õ™Ø|pCPÀÒʨ?JÑ Ê%¸ µ’¼(qǜϤå€QP@ë§¥¥-\¸Ð•|H°¿˜‚° JJJp†€G®aØÿh,@šÒä&´IEND®B`‚jsamp-jsamp-1.3.7/src/site/resources/images/bridge2pre.png000066400000000000000000000100121356450004300235030ustar00rootroot00000000000000‰PNG  IHDRºõhbx pHYsM&á…³ŒtIMEÙ+¨!W5©IDATxÚí{tÕdzټ Â&b0h$€¼”dZ Æ€4¦è1èI)±-UI"¢(Pÿ€sB¤6!»i©G)Š$¥ƒÆ£©›ƒ„—HÈûI älÿºŒ3³“ÝÙÙGv¿ŸÃáìÌÞùÍ̽ßùÝï½s˜Èý:À2T¨¹¸'ù÷ÅÊø[AÔ¼K±øS…h<Çk%þT¡µK+ÏÚæVËâš¶™²Šá~Å-É;ŠÛÜèŒ<4»ØÚI§ô>î—]“‹°»±êÄÒRc¿¦;‰bÂë^ /ˆhLé3Zr%VÕ÷âmo6Í.欉…§6¯_”¡?žDx1]3¸~v‘áAÕΪJ馠›–~ÐÍ¥sWÂÝäÅ=‘ðñ°0‚²‹ M«ì§ÓÍ[RõN4–_ G™ eºwž2D#Ø#»8Èê*«k»ç!“§´)±½Ë°¶ºé}¤ƒ¸ôÈH©¾Vz/Zž÷Ùª&Q¤;òJœbw\Ô»˜« «&ñØÂ¼Î[è„÷ã:s€®6éºÞEÈ1ÓÈÓü‚°Ç•ب?—ö.²kнJK|†#g·D«oÈÁˆô­Y{ÏÒɘËqºwaLë],îâsnŒ‚Š‘×ÜjWî­]³‹Œæf°šÎ“¹;‚ Àš‘Ñâs]¨€ìì]:kÏ¡€‰ÔÔTÑýz½^§Ó©%JO#&&¦¤¤€w p´Z-ä,ÕJjª.&&ri¯ÈÀêÛ0 æ¾’³´›k˜M¡Ùì&÷3ޤ¦Æèõ% È…'­V+”…l¡@g.®µÍ)Ú´BI™KEœē h&SŒ£½‹¨ x²àÉK¨*´Ÿãá½BR[bPìôd[“×ß §ý*ߊÎHA‰ÈkcSoÅU ÒŒ³šÃqiÙЍ‰Î“JFãqÃ9 ‘Nè(ZÎ@ší „ƒEÊs ›z"ˆÆYŠŽŒÔJ=î£k鉉ˆ«)/€XÀ,E§KåÍ»@.ÀR­@.À ­@.@|(cîç# °‚¡Ò’?SÃn¤cg¹GS\üG5ïø>óLí…`¿ ™ÓŽ_>Â/°³Ã›ý6pÄà$0W¡ÄâGßÜÏmmËWþþÐÉo¿@߀OOæ/Ý·lÿS%×ö±ßÖ}8bí4…¥×ëefstþx±¡â…9Ï$ݵžˆ(š|‚›*š*žß½§%ï…˜/4þüàµ?IMMæ›äÒÞÝÁÛÓÛ®ç£yõ>-ù½03xqoE¡´Z†ˆ £è¦%‡;å`Sg4gôˆQ{Šºþ¹&¬ŸŸ{ê&¡Æ‡­#Ž1åż˵«êuñij/õ;_¼ûdîㆎ½¼||~ܵZ†M$¼Ï¢€]½‹­VwŠÿ¢½ÏÜzW[gûŸd>Ÿ7oÝñO_Ë7¾Ç17¦Õ2lÅØÃ»(,—Þ^RõïLÚ·%aËÜ»g žª>÷öÑ·æfφÏWMŒYàãßx½iå'é-~'Ðí]ŽŸ/ 5vñýqìæ¡“OïzbÝñä6ÿ¯}|©§}̲¨ÍÛ—ì ¢ÎÞ®ÍG2•²)hHçz™ó.Ûé+/½;òÅf¦¬;òzES•VŸOù0-ÀÇ?,Xããå]ÓVÏž7i®-×m0YCÃ~0§!ta˜w‘)c÷˜…3/z‘oWÔ¶Ç>=T™y¸ôH{çµAã`go×¥¦ª›ÑU^Kî*áÎ×­u¬æ6¹û¡»zv>—ç]äÏêža¦SïÍÏIÒ–Þû‡¶þ+Å5ÿ>Ww±¬ñMŸœýô¨¾úQÿÃ>¯Ø*.}}D}¾4ažfÂ< ç‹.@Ýgï"çQ Ö¼ ÇX(áo“À°V€s¼ —Ʀ¦ŽŽƒƒ7—¶Œ Õ mà]DÐér+këÃÂïT{©|¼½ëªk|T>ööe/%£Þ=Ñ»H’ò›‹–/~zäm£ü¼UF†ìé»ÚÜòŸ‚cë×oxï½M¨wwÊ1òßõôôdeë,Z={VШQW¯µ_ilili¿ÚÑ¢óX|ü„‰“vîü°§Ç¢WÓ Ã0œ·‘¼MK‘ÞoI@kOäÆ(¿Þ¥¶®îzWÏÜ_=r{ø—««8]¶qëûzóÝÂâë[ÛCÃ5óæ?ÖÔÜ\SSã”æ‰Ï–PF£Ñh4z”t”_ï²ñ½­ã#ï ¹mÄ•†Ö²ªúÔç–æésìÛÿÇ”å†ÏTÖ^:zÚô[·nµ½áM-¤Tæ`8ãHìÕ¢t¨aí]“‹Jí}WäxÆKíë­:—×ÙÝóåÉS=üpWÿ±‚oµqQqýúu{göÑç6Œ)p ð’)gðZT4 /,7ÙXÊmrŒ Þ¥óFc}Ó`¿—JÀ殮;}öÍ›¼U^=]}ý==gÏž5®­zè­ê),é èÄPÃÞ»¨¦¦²òûoP“1>.Î?hdQáW­­ÍïoxûÙÄg4c‚‹¾ùÞÛK­Ñh¬ª_®Q®æ]ä¤7oz;믺KåAwhFgéuùùÌààâ'ŸôU{ÿ|þROGÇ7E_¿òê«N¹a£ÑÈ&*ˆÏ%æ]ÂBÃ4a¡ 5Õ×®w†ED öv?±`Áˆ€ÀúÚj_#Õ´µÖ×]7.âÎqãì1ØÃlÞÅ×Ïwuúk—+*ŒýÝu•—»;½7Ú›#ƆÕTU´µ5½øÓë«Wùùù)âÌå “×~k®_34w %Ã=V^(Ïò.,Û?ئR;¯6•9ù÷_¾=ñÍñÕÕ]Qûªvým‡¼¹ ‰Mî~ã/‘6˜¢%Njî³Ð`  Þå¼¼by{{{ss3èÓßתÁ+Fx3£Šá]€'b/ï¥rŒüìWGD ù DÔQß¡IYu8Ÿ 'ºõ?VYópKäöm‘¹×˼e‘ìì;²‹íÞEôwF6x—ÿ½Da+ÂÂâÃn¿¿õ.¼ÖGvVä©ì¢ÕjQeë]ؼÂó.fåb0PkžWxZAg†ð. ï]  î]L9rVçÈ á] `jnG€4ŒhÎnß­ÈÏ.:uêÆ¦UøêÇ&¹Þ#º/z½^Át« † 77·«««°°°°°rCh%99¹¸¸ÙX§•²²25*˜ØŸÛS}k“§N¹€[¹$''‡ˆŠŠŠØ=<­D¯ž ¹x:oýeÍxÿ‰D”“““‘‘!,Àjåµ3²“v»x4ë·¯Éÿèxii)IheÖÚéÙI;au=kåÝD”––VZZZVVf®ØŽ¤Ù2;#v¯i&oSqìŸ8 “v–Ü”ýn|ç9µ^D”——™–––™™IDQQQG ï2d}ÙX­£K±óHNì”Ç“6$>|xË–-iii‘‘‘‰‰‰VŒÚ~Ϲ‡I«Õ²;EhÞϸ%y¡LL›¦È¢Á…À+)¼rᥚ È»k•Ç=J4‚²‚¨ózc÷UUUéééDÄ*&66–ˆ¸Š)++kmm b:Ð^ÞE´¢…ûyeL;%ê…[†-f*,šçÍ]€°ùyç^êÍݵ*Óbbb²³³322Ö®]›™™YQQ›˜˜ÈúÖÊäåå‹»ÃöVêù¶ºRýšµ%¹Å,9DF“+ÛKæææšº›ììl"Í13fÌÐét3Wß«n·«xyuÇ5¤b\ógu¢]ªÃЖ51jjzzzFFÆìÙ³¥CD¹ÏýÃ…¬®ì a‰bðË:!LƒOúÆtÓ‹9ÅDDDDGG7Œ¹<y\ƒéN8à¦LsüŠùî»ïFÎ8žõµ2r‘/X•!xãÓN 5ˆž”õ¤Âඤ@£IŸBôÆí='ÄSÌ’%K̈IMMÕét.¸šÎÓæBìeVôznûòÖ$˜`ÇA:nåÊ•Ÿ_ÚpSþ𘦳%c!ÇDÒZ!¢Ï*ö}º¹ÀqÓtN1¿ÀZ­pßµ¶¶æååM[µ+yt,`ðP­p×{xÀoªZ\<¡V¦®šðQò^kã`ƒGÀÓÊ”×ährñ,X­LZ¹{Ù^yÐyì¼~llìŠ+l‰£&óÿ˜<ƒÁ€ÁíQjö™ílDi IEND®B`‚jsamp-jsamp-1.3.7/src/site/site.xml000066400000000000000000000024521356450004300171700ustar00rootroot00000000000000 jsamp-jsamp-1.3.7/src/site/xdoc/000077500000000000000000000000001356450004300164345ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/site/xdoc/api.xml000066400000000000000000000125131356450004300177310ustar00rootroot00000000000000 API Overview Mark Taylor jsamp-jsamp-1.3.7/src/site/xdoc/bridge.xml000066400000000000000000000215571356450004300204240ustar00rootroot00000000000000 Bridge Mark Taylor

JSAMP provides a special client called the Bridge. Its purpose is to link up two or more hubs, so that clients registered with one of the linked hubs can communicate with clients registered with any of the other hubs as if they were registered on the same one. Usage instructions are given in the commands reference.

The bridge works by registering a proxy client on all the remote hubs for each client on a local hub. These proxy clients can be communicated with just like non-proxy ones, but the client metadata is adjusted so that a user can see that they are proxies; the icons are mangled, the names have "(proxy)" appended, and a metadata entry with the key "bridge.proxy.source" is added to indicate which hub the client is proxied from.

You can see some slides from October 2008 explaining how it works here: Bridge presentation.

In order to set it up, you need to specify which hubs will be connected. There are various ways you can do this, as explained in the usage documentation. In most cases, you will want to bridge the local 'standard' hub with a remote one. If you can see a .samp file (for instance from another user's home directory) you can specify it by name like this:

   jsamp bridge -sampfile /home/foo/.samp

or by directory like this (the name ".samp" is assumed):

   jsamp bridge -sampdir /home/foo

or by URL like this:

   jsamp bridge -sampurl file://localhost/home/foo/.samp

If you can't see the SAMP lockfile (for instance if it is on a remote machine), you can specify the XML-RPC endpoint and samp.secret string directly. You might exchange this information with a collaborator by email or instant messaging if you want to set up a collaboration. In this case, one (not both) parties would execute a command like:

   jsamp bridge -keys http://foo.star.com:46557/xmlrpc 3c3135435de76ef5

The keys here are obtained by looking in the remote user's .samp lockfile (the samp.hub.xmlrpc.url and samp.secret entries respectively). Note that if the URL uses a loopback hostname like "localhost" or "127.0.0.1" you may need to replace it with the fully qualified domain name.

By default the local standard-profile hub is used in addition to any others specified on the command line, but you can inhibit this with the "-nostandard" flag; for instance the following sets up a three-way bridge between the standard profile hubs running in the home directories of users foo, bar and baz:

   jsamp bridge -nostandard
                -sampdir /home/foo -sampdir /home/bar -sampdir /home/baz

without the -nostandard flag it would have been a four-way bridge between the hubs of foo, bar, baz, and the user running the command.

By use of the -profile flag, non-standard profiles can be used as well. The bridge can thus enable two communities of SAMP clients using different profiles to communicate together, with no other changes to the client source code or configurations.

The bridge can be run programmatically using the org.astrogrid.samp.bridge.Bridge class as well - see the javadocs.

If you want to experiment with the bridge without having multiple accounts or multiple hosts you can do so by using the SAMP_HUB environment variable to set up different hubs and communities of clients for connection with bridges.

   // Start up a hub and a client using the standard profile.
   java -jar jsamp.jar hub
   java -jar jsamp.jar hubmonitor

   // Start a hub and client using a non-standard lockfile location.
   setenv SAMP_HUB std-lockurl:file://localhost/tmp/samp1
   java -jar jsamp.jar hub &
   java -jar topcat.jar &
   unsetenv SAMP_HUB

   // Bridge them together.
   java -jar jsamp.jar bridge -sampfile /tmp/samp1

The syntax here is C-shell-like - something similar can be done in other environments.

In principle, once set up the bridge should work with no further intervention. However, in the case of a bridge between different machines, there may be some communications issues related to URLs. Although URLs are formally intended for locating a resource regardless of where you are, in practice there may be problems accessing a URL from a machine other than the one it was constructed on. These problems fall into two categories.

  1. SAMP clients and the hub usually use HTTP over high-numbered ports for communications. If either machine is behind a firewall which blocks such ports, the bridge won't work. There's not much you can do about this, other than reconfiguring or disabling the firewall.
  2. Many of SAMP's MTypes work by exchanging URLs between clients, for instance table.load.votable messages pass the location of the table to load as a URL. Although in general a URL is intended to specify the absolute location of a resource, in some cases a URL can only be resolved on the machine on which it was generated. This is true in two main cases: firstly if it is a "file://..."-type URL, which is specific to the current host's filesystem, and secondly if it names the host in some way other than with its fully-qualified domain name, for instance using a loopback name such as localhost or 127.0.0.1. Exchanging such URLs between bridged hubs on different hosts will not work. The bridge can mitigate these problems to some extent, by attempting to export problematic URLs. In this mode, strings in message bodies and responses which appear to be URLs are examined, and if they appear to be host-specific for a remote host, they are rephrased to make them remotely readable. This involves replacing loopback hostnames with fully-qualified ones, and where possible exporting file-protocol URLs with HTTP ones. File URL exporting can only work from the host that the bridge is running on though, not in the other direction. For this reason it may be a good idea to have the bridge running on the machine providing most of the data rather than the one receiving most of the data, if there's a choice. This URL exporting is done by default if there appear to be multiple hosts involved in the bridge, but may be controlled by supplying the -[no]exporturls flag on the bridge command line. This kind of message manipulation necessarily involves a bit of guesswork, so it is possible that it may cause problems.

The bridge is somewhat experimental; what (if anything) it's useful for and whether it needs adjustment or new features are still under consideration. Please contact the author or discuss it on the apps-samp list if you've got any comments or queries.

Here is an illustration of how it looks when two hubs have been connected together using a bridge.

Prior to the bridge we have two separate hubs, as shown in the images below. The left hand one shows a JSAMP hub (displayed using the JSAMP graphical hub view) with TOPCAT registered. The right hand one shows the state of a SAMPy hub (displayed using the JSAMP hubmonitor), running on a different machine, with registered clients HubMonitor and SPLAT.

Following a suitable bridge command, they look like this:

As well as the original, directly registered clients, each hub now has the bridge client itself, and a proxy copy of each (non-hub) client registered on the other hub. The proxy ones are easy to identify because their names (samp.name metadata) have "(proxy)" appended, and their icons (samp.icon.url metadata) have been adjusted (currently the bottom right corner is cut off, though some other visual idiom may be used in future releases). In other respects these proxy clients appear to other clients on their remote hubs just the same as any other client, and can be treated accordingly.

jsamp-jsamp-1.3.7/src/site/xdoc/commands.xml000066400000000000000000000704131356450004300207640ustar00rootroot00000000000000 Command-line Tools Mark Taylor

JSAMP provides a number of command-line applications for standalone use in various contexts. These come with their own main() methods so can be invoked directly. A convenience class org.astrogrid.samp.JSamp is also provided (with its own main() method) which might save you a bit of typing when running these. So for instance to run the hub, which is in class org.astrogrid.samp.hub.Hub, you can execute either:

   java -classpath jsamp.jar org.astrogrid.samp.hub.Hub
or, more simply:
   java -jar jsamp.jar hub
As a special case for convenience, simply running "java -jar jsamp.jar", or clicking on the jar file in some OSes/graphical environments, will start the hub, along with a short usage message to standard error.

In all cases, supplying the "-h" or "-help" flag on the command line will print a usage message.

The JSamp usage message (java -jar jsamp.jar -help) says:

The individual command-line applications are described below. They have their own specific command-line flags to control use, but most share the following common flags:

-help
Outputs the usage message. May be abbreviated to -h.
-/+verbose
Increases/decreases verbosity. This controls what level of logging is performed by the application. By default, WARNING (and higher) messages are output, while INFO (and lower) messages are suppressed. -verbose increases the verbosity by one level and +verbose decreases it by one level. These flags may be supplied more than once. May be abbreviated to -v or +v.

The various system properties listed affect communications for these applications in the same way as for JSAMP applications in general; see the section on System Properties for more detail.

The GUI window, which is used by several of these tools to display the clients currently registered with the hub along with their metadata and subscriptions, looks something like the screenshot below - though see the GUI Features section for more detail.

HubMonitor screenshot

The org.astrogrid.samp.hub.Hub class runs a SAMP hub. A graphical window showing currently registered clients and their attributes (metadata and subscribed MTypes) may optionally be displayed. By default the hub operates using the SAMP Standard Profile, but there are options to use different profiles, including user-defined ones, as well or instead. Some of the flags relate to particular profiles.

Hub usage is:

-mode no-gui|client-gui|msg-gui|facade
Determines what hub implementation is used; currently this affects whether and how the hub status is displayed graphically. The following options are available:
no-gui
There is no graphical display.
client-gui
A window is displayed showing which clients are registered and their metadata and subscriptions.
msg-gui
A window is displayed showing clients with metadata and subscriptions; it also gives a graphical representation of what messages are being sent and received between clients. In the case of heavy messaging traffic, the extra processing required for this display can slow down hub operations a bit.
facade
This option piggy-backs the hub on an existing hub. The hub passes any registration requests to an underlying hub. The underlying hub is located using the usual arrangements for client hub discovery (influenced by the SAMP_HUB environment variable). This can be used to provide additional access to an existing hub using a profile which the existing hub does not implement. There is no graphical representation in this case.
In the case of the GUI options, they attempt to insert an icon into the "System Tray" area of the desktop, if one exists, and if a java version >=1.6 is being used. A menu available from this icon can be used to display or hide the window. If the system tray is not accessible (java version <1.6 or not supported on desktop), then the window will be displayed directly, and closing the window will shut down the hub.
-profiles std|web|<hubprofile-class>[,...]
-extraprofiles std|web|<hubprofile-class>[,...]
These two flags identify which profiles the hub will run. The -profiles flag determines which profiles will be running initially, and the -extraprofiles flag specifies additional profiles which may be available to start manually, for instance from the Profiles menu of the hub window. In each case a comma-separated list of profile identifiers is used. If no -profiles flag is specified, then the default set of profiles is used; at present this equivalent to -profiles std,web and no -extraprofiles. The flags may be given more than once, in which case all the named profiles will run. The following options are available:
std
Standard Profile. The -std: flags below relate only to this profile.
web
Web Profile. The -web: flags below relate only to this profile.
<hubprofile-class>
If the name of a class which implements the HubProfile interface and which has a no-arg constructor is given, that profile is run.
These flags have a similar usage to the jsamp.hub.profiles and jsamp.hub.profiles.extra system properties, but overrides them, and must be used to take advantage of the corresponding profile-specific flags.

The following flags are used only if the Standard Profile (-profiles std) is in operation:

-std:secret <secret>
Optional flag to supply the samp.secret string which will be written to the Standard Profile lockfile and which clients must present on registration. If not supplied, a random string will be chosen.
-std:httplock
If this flag is supplied, the Standard Profile lockfile is not written to a file, but is made available via HTTP instead. The hub will print the location, in a form suitable for use with the SAMP_HUB environment variable, when it starts up.

The following flags are used only if the Web Profile (-profiles web) is in operation:

-web:log none|http|xml|rpc
Provides different levels of logging of the Web Profile communications. This logging is to standard error, and not performed through the Java logging system. The following options are available:
none
No logging.
http
All HTTP communications, including HTTP headers and in most cases HTTP request/response bodies, to the web server hosting the XML-RPC server, are logged. This can be very useful for debugging communication issues, since both HTTP headers and non-XML-RPC HTTP requests are essential parts of the various sandbox-busting technologies required by the Web Profile.
xml
The XML text of all XML-RPC communications is logged.
rpc
An interpreted summary of the content of the remote procedure calls is logged.
-web:auth swing|true|false|extreme
Configures how the Web Profile hub determines whether clients are permitted to connect. The options are:
swing
A popup dialogue is shown to the user for every registration request. Registration is only granted if the user explicitly authorizes it. This option is the default and should usually be used.
true
All requests to register are granted without reference to the user. This can be convenient during testing, but should be used with care, since it can facilitate access for potentially hostile browser-based clients.
false
All requests to register are refused without reference to the user. This rather silly option means that no clients can register via the Web Profile.
extreme
Solicits confirmation via a popup dialogue from the user as for swing, but takes extra measures to try to discourage the user from accepting requests.
-web:[no]cors
Configures whether the Web Profile HTTP server will allow access from browser-based clients using W3C Cross-Origin Resource Sharing. By default this is currently turned on.
-web:[no]flash
Configures whether the Web Profile HTTP server will allow access from browser-based clients using Flash-style crossdomain.xml files. By default this is currently turned on.
-web:[no]silverlight
Configures whether the Web Profile HTTP server will allow access from browser-based clients using Silverlight-style clientaccesspolicy.xml files. By default this is currently turned off.
-web:[no]urlcontrol
Configures whether restrictions are applied to the URLs that the Web Profile translation service will translate on behalf of clients. If set on, local URLs will only be translated if they have been mentioned in earlier messages or responses to a web profile client. By default this is currently turned on.
-web:[no]restrictmtypes
Configures whether restrictions are applied to the MTypes that Web Profile clients are permitted to send. If set on, only common MTypes with known semantics are permitted (this includes samp.app.*, table.*, image.* etc); attempts to send messages with unknown MTypes (with possibly harmful semantics) are blocked by the hub. By default this is currently turned on.

For convenience, the hub can also be started simply by invoking the jar file with no arguments (for instance clicking on it).

To run a hub with default settings (standard profile only) do this:

    java -jar jsamp.jar hub
and to run it with both Standard and Web Profiles, do this:
    java -jar jsamp.jar hub -profiles std,web

The org.astrogrid.samp.gui.HubMonitor class runs a SAMP client which connects to any available hub and displays a window showing currently registered clients along with their attributes (metadata and subscribed MTypes). If no hub is available at startup, or the hub shuts down while the monitor is running, the monitor will poll for a hub at regular intervals and reconnect if a new one starts up.

A button at the bottom of the window allows you to disconnect from a running hub or to reconnect. While disconnected, no automatic connection attempts are made.

The HubMonitor class itself is a very simple application which uses the facilities provided by the other classes in the toolkit. See the source code for details.

HubMonitor usage is:

-/+verbose
See above for the description of verbosity setting. If -verbose is used, each message sent and received will be logged to standard error through the logging system.
-auto <secs>
Sets the number of seconds between reconnection attempts when the monitor is inactive but unregistered. If <=0, autoconnection is not attempted.
-nogui
The monitor registers as a client, but no window is displayed.
-nomsg
Normally the window displays an indication of pending messages sent and received by the monitor itself. If the -nomsg flag is given, these will not be shown.
-mtype <pattern>
Gives an MType or wildcarded MType pattern to subscribe to. This flag may be repeated to subscribe to several different MType patterns. Like the Snooper command, it does not actually understand MTypes subscribed to in this way, so it sends a response with samp.status=samp.warning. If omitted, only the administrative MTypes (required for the monitor to keep track of clients) are subscribed to.

The org.astrogrid.samp.util.SampLoad class takes a filename or URL on the command line and pops up a window allowing the user to load that resource into a selected running SAMP client, by sending a message with a load-like MType. The main intended use is as a browser helper application: you can associate it in your browser with suitable MIME types so that it opens e.g. VOTables and FITS files by default, or simply select it at a browser's Open with ... prompt for files that you think or know are e.g. VOTables or FITS files.

SampLoader usage is:

-/+verbose
See above for the description of verbosity setting.
-rtype votable|fits|cdf
If provided, this forces the type of resource that the <file-or-url> is presumed to represent, which determines the SAMP message type with which it will be sent. If omitted, SampLoad will work out the resource type itself.
<file-or-url>
The name of a file or URL at which the resource to load can be found. When used as a browser helper application, this will normally be a temporary file that has been downloaded by the browser.

To use it with a browser, you may be able to find a sampload executable or sampload.jar jar file; otherwise you will need to set up a short shell script or similar, like:

    #!/bin/sh
    java -jar /path/to/jsamp.jar sampload "$@"

Then when it is run by the browser to handle a downloaded file, you will see something like the following screenshot:

SampLoad screenshot

The file types it can currently deal with, and the corresponding MTypes it sends are:

  • VOTable (table.load.votable)
  • FITS image (image.load.fits)
  • CDF (table.load.cdf)
It identifies the file type by examining the file content itself, or its HTTP headers if available, though this can be overriden by the -rtype flag if required. If you want to make SampLoad a default application for certain MIME types in your browser, the following types may be useful:

  • application/x-votable+xml
  • application/fits
  • image/fits

though note that data providers do not always annotate their data links with these MIME types.

The org.astrogrid.samp.test.Snooper class runs a SAMP client which subscribes to some or all MTypes and logs each message it receives to the terminal. This can be useful for debugging, especially for testing whether your application is sending messages which look right. Since it does not actually understand the messages which have been sent, it sends a Response with samp.status=samp.warning.

Note that the HubMonitor command can also be used in this way; Snooper is useful if you would rather have information presented on standard output than in a GUI.

Snooper usage is:

-mtype <pattern>
Gives an MType or wildcarded MType pattern to subscribe to. Subscription to the given MType is with an empty annotation map, so this is identical to "-subs <pattern> {}". This flag may be repeated to subscribe to several different MType patterns. If both this and the -subs flags are omitted, a value of "*", i.e. subscription to all MTypes, will be assumed.
-subs <pattern> <subsinfo>
Allows subscription with a given MType or wildcarded MType pattern and a non-empty annotation map. The <subsinfo> parameter should be a JSON representation of a map representing the annotation of the MType(s) given by <pattern>.
-clientname <appname>
Specifies the samp.name metadata item which the sending aplication should give for its application name following registration.
-clientmeta <metaname> <metavalue>
Specifies additional items of metadata for the sending application to give following registration. The metavalue items may be in SAMP-friendly JSON format, or just plain strings.

The org.astrogrid.samp.test.MessageSender class can send a simple SAMP message from the command line and display any responses received in response.

MessageSender usage is:

-mtype <mtype>
Gives the MType for the message to send.
-param <name> <value>
Gives a named parameter for the message. This flag may be repeated to pass more than one parameter. <value> will be interpreted as a JSON structure if possible (note double-quoted strings are the only permitted scalars), otherwise the value will be interpreted as a plain string.
-targetid <receiverId>
Specifies the SAMP public ID for a client to which this message will be sent. This flag may be repeated, or combined with -targetname, to send the same messsage to more than one recipient. If neither this nor -targetname is supplied, the message is broadcast to all clients which are subscribed to the MType.
-targetname <receiverName>
Specifies an application name (samp.name metadata item) identifying a client to which this message will be sent. Names are matched case-insensitively. If multiple clients with the same name are registered, only one will be messaged. This flag may be repeated, or combined with -targetid, to send the same messsage to more than one recipient. If neither this nor -targetid is supplied, the message is broadcast to all clients which are subscribed to the MType.
-mode sync|async|notify
Specifies the delivery pattern to be used to send the message. In the case of notify, no responses will be received. The sender only declares itself callable if async mode is used. The default is sync.
-sendername <appname>
Specifies the samp.name metadata item which the sending aplication should give for its application name following registration.
-sendermeta <metaname> <metavalue>
Specifies additional items of metadata for the sending application to give following registration. The metavalues may be in JSON form or plain strings.

The org.astrogrid.samp.test.HubTester class runs a series of tests on an existing SAMP hub. Most aspects of hub operation are tested, along with some moderate load testing. In case of a test failure, an exception will be thrown, and under normal circumstances the stackdump will be shown on standard error. These exceptions may not be particularly informative; hub authors debugging hubs will have to examine the HubTester source code to see what was was being attempted when the failure occurred.

Normally, if a hub passes all the tests there will be no output to standard output or standard error. Under some circumstances however a WARNING log message will be output. This corresponds to behaviour that a hub implementation SHOULD, but not MUST, display according to the SAMP standard.

It's OK for other clients to be registered while the test runs, but such clients should not either register or unregister while the test is under way - this will confuse the test and probably result in spurious test failures.

HubTester usage is:

-gui
If supplied, a HubMonitor window will be shown for the duration of the test.

The org.astrogrid.samp.test.CalcStorm class runs a number of clients simultaneously, which all connect to the hub and then send each other messages. A private set of MTypes which provide simple integer arithmetic are used. Checks are made that all the expected responses are obtained and have the correct content. On termination, a short message indicating the number of messages sent and how long they took is output. This application can therefore be used as a load test and/or benchmark for a given hub implementation.

CalcStorm usage is:

-nclient <n>
Gives the number of clients which will run at once.
-nquery <n>
Gives the number of queries which each client will send to other clients during the run.
-mode sync|async|notify|random
Specifies the delivery pattern by which messages are sent. The default is random, which means that a mixture of modes (approximately a third each of synchronous call/response, asynchronous call/response and notification) will be used.
-gui
If supplied, a HubMonitor window will be shown for the duration of the test.

The org.astrogrid.samp.bridge.Bridge class provides a connection between two or more different hubs. If run between two hubs, A and B, every client on A also appears as a 'proxy' client on B, and vice versa. A bridge client also runs on both hubs A and B to keep track of which clients are currently registered, so that it can generate new proxies as required. The effect of this is that clients registered with one hub can send and receive messages to and from clients registered on a different hub, just as if they were local. This can be used to facilitate collaborative working, though you may be able to think of other uses.

To run it, you must specify which hubs to bridge between. In most cases you'll want one to be the default (standard profile) hub, so this is assumed by default. You therefore only need to specify how to connect to the non-default hub(s). You can do this by using one or more of the command-line options described below.

Bridge usage is:

-exporturls/-noexporturls
With -exporturls, an attempt is made to translate URLs in message bodies and responses from localhost-specific forms to remotely accessible ones, for instance renaming loopback addresses like "127.0.0.1" as the fully qualified domain name. With -noexporturls this is not done. The default is to export URLs if the bridge is apparently running between hubs on different hosts.
-standard/-nostandard
-standard indicates that one of the hubs to bridge is the Standard Profile hub. This is default behaviour; -nostandard means do not include the standard profile hub.
-sampfile <lockfile>
Names a file in standard-profile format which describes the location of a hub. This flag may be given more than once to specify multiple non-standard hubs.
-keys <xmlrpc-url> <secret>
Gives the XML-RPC endpoint and "samp-secret" string required for communicating with a running hub. These correspond to the samp.hub.xmlrpc.url and samp.secret values from the standard profile lockfile. This flag may be given more than once to specify multiple non-standard hubs.
-sampdir <lockfile-dir>
Names a directory containing a file ".samp" in standard-profile format which describes the location of a hub. This flag may be given more than once to specify multiple non-standard hubs.
-profile <clientprofile-class>
Gives the fully-qualified classname of a java class which implements the ClientProfile interface and which has a no-arg constructor. A newly-constructed instance of this class will be used to contact a hub. This flag may be given more than once to specify multiple non-standard hubs.

The bridge is a bit experimental, and there are a few subtleties concerning its use. Some more discussion can be found on the bridge page.

jsamp-jsamp-1.3.7/src/site/xdoc/debug.xml000066400000000000000000000054501356450004300202500ustar00rootroot00000000000000 Debugging with JSAMP Mark Taylor

JSAMP provides many facilities which are useful for the application author when adding SAMP capabilities to new or existing applications, or when testing SAMP communications between tools and trying to see what is or isn't working. In many cases this applies whether or not you are using JSAMP for your own SAMP implementation.

Here is a short summary of some of the most useful of these facilities. It is not exhaustive.

JSAMP's GUI hub view represents pretty much the entire state of the hub in graphical terms, including all the messages which are passing through and their responses. If you start the JSAMP hub

   java -jar jsamp.jar hub -mode msg-gui

then you can see all the messages, including their text and any errors and so on in the responses. See the GUI section for examples.

You can get a similar view from a client's-eye view using the HubMonitor in GUI/message tracker mode to view a third party hub, though it doesn't have access to as much information as the Hub GUI itself (it can only see the messages it participates in itself).

You can see all the details of of the XML-RPC exchanges between SAMP clients if you set the jsamp.xmlrpc.impl system property appropriately on one of the clients or the hub or both (but note the output can get quite verbose). This is good for diagnosing bad XML-RPC sends/receives, and also for getting concrete examples of what the POSTed XML text looks like if you are constructing your own messages by hand.

When using the Web Profile, running the hub with one of the -web:log options can be very helpful, since it shows all of the traffic to and from the Hub Web Profile HTTP server.

If your application is waiting for messages with a given MType, but you don't have another one to hand which can generate such messages, you can use the MessageSender tool to generate messages to order and send them from the command line.

Conversely, if your client is sending messages but you don't have a recipient for them, you can run Snooper subscribed to the MType(s) in question to receive the messages and see what they look like.

jsamp-jsamp-1.3.7/src/site/xdoc/deployment.xml000066400000000000000000000146151356450004300213450ustar00rootroot00000000000000 Deployment Mark Taylor

JSAMP is a pure Java library. It should run on any Java Runtime Environment of version J2SE1.4 or above. It does not require any external libraries, though see below.

In order to perform the necessary XML-RPC communications for use with the SAMP Standard Profile, a pluggable layer is used. This means that you can choose which XML-RPC library is used. Currently, there are the following possibilities:

Internal
A standalone library implementation is provided within JSAMP, so if you wish to use this, no external dependencies are required.
Apache XML-RPC 1.2
To use the Apache XML-RPC library, the Apache classes must be on the class path. They can be found in the jar file xmlrpc-1.2-b1.jar.
Roll your own
By implementing the class org.astrogrid.samp.xmlrpc.XmlRpcKit you can provide your own implementation, perhaps based on a third party XML-RPC library.

In some cases you can choose which of these is used by supplying particular implementations of the classes in the xmlrpc package. Mostly though, an implementation will be chosen without any explicit choice in the application code. In this case, you can control which implementation is used by setting the value of the jsamp.xmlrpc.impl system property.

The internal implementation was introduced as an experimental measure, but it seems to be quite reliable and of comparable speed to the Apache one. The internal implementation is therefore recommended for most purposes.

If you are embedding JSAMP into your own application code but want to keep the size of the additional class files to a minimum, you can avoid incorporating all of the classes in the built jar file. The subordinate packages are arranged in a modular way. They are organised as follows:

org.astrogrid.samp
Basic classes required throughout the library. You need these whatever you're doing.
org.astrogrid.samp.client
Classes intended for use by application authors writing SAMP clients.
org.astrogrid.samp.hub
Classes providing the hub implementation. If you are only writing a client which will not contain an embedded hub, you do not need these classes.
org.astrogrid.samp.gui
Utility classes providing GUI components for visual display of SAMP activity. These classes are not required for basic client or hub operations.
org.astrogrid.samp.test
Classes providing the hub test suite and some diagnostic tools intended for debugging SAMP systems. These are not intended for use in third party applications. This does not however contain unit tests for JSAMP itself, which are not present in the distributed jar file.
org.astrogrid.samp.bridge
Classes for the Bridge application, which can connect two or more hubs together.
org.astrogrid.samp.httpd
Simple HTTP server implementation. This is required by the internal XML-RPC implementation. It may also provide useful utility functionality for client applications who wish to serve resources over HTTP.
org.astrogrid.samp.xmlrpc
Classes which deal with XML-RPC based communications and the implementation of the Standard Profile. Everything in the packages above is independent of the Profile used, so if for some reason you are using a non-standard profile, you may not need the xmlrpc* packages. The classes here define a pluggable interface to XML-RPC implementations; one or other of the provided implementations, or a custom-written one, will be required as well to make use of these classes.
org.astrogrid.samp.xmlrpc.apache
Implementation of the pluggable XML-RPC layer based on the Apache XML-RPC library. To use these classes you will need the Apache XML-RPC library on the classpath as well. If you are using the internal XML-RPC implementation, these classes are not required.
org.astrogrid.samp.xmlrpc.internal
Implementation of the pluggable XML-RPC layer which requires no external libraries. If you are using the Apache library or a home-grown XML-RPC implementation, these classes are not required.
org.astrogrid.samp.web
Implementation of the Web Profile hub and clients. The Web Profile is XML-RPC based, and requires some of the classes in the org.astrogrid.samp.xmlrpc* packages as well.
jsamp-jsamp-1.3.7/src/site/xdoc/downloads.xml.vm000066400000000000000000000034421356450004300215740ustar00rootroot00000000000000 Downloads Mark Taylor
Current Version (${jsampVersion}):
Unsigned jar file:
jsamp-${jsampVersion}.jar
Signed jar file:
jsamp-${jsampVersion}_signed.jar
Other versions:
All previous releases are available from the FTP archive at ftp://andromeda.star.bristol.ac.uk/pub/star/jsamp. This may also contain pre-release versions. Older versions are also still available from their historical home on the Astrogrid Maven server.
Source code:
The source code is available on github at https://github.com/mbtaylor/jsamp/.
Dependencies:
The only (non-J2SE) external library which is sometimes used by JSAMP is Apache XML-RPC: xmlrpc-1.2-b1.jar. Note however that this library is not necessary for using JSAMP, it is only required if you want to use the Apache XML-RPC implementation. If you want to use the internal XML-RPC implementation instead, which is generally recommended, then no extenal libraries are required.
jsamp-jsamp-1.3.7/src/site/xdoc/gui.xml000066400000000000000000000134021356450004300177420ustar00rootroot00000000000000 Graphical Features Mark Taylor

JSAMP offers a graphical view of SAMP status in two basic contexts:

  • If you are using the client toolkit, you can see what the client knows about hub status
  • If you are running a hub, you can see the internal state of the hub

These facilities are available both using the command-line clients described in commands and directly from the API. The classes you use are different for the hub and client views, but they share similar class hierachies and graphical representations.

In both cases, there are currently three levels of GUI which can be used:

  1. No GUI: (classes client.HubConnector, hub.HubService)
  2. Client list: An automatically updated list of registered clients with their metadata and subscriptions (classes gui.GuiHubConnector, gui.GuiHubService.html) is available.
  3. Message tracker: As well as the list of registered clients with their metadata and subscriptions, lists of all messages sent and received by visible clients (classes gui.MessageTrackerHubConnector, gui.MessageTrackerHubService) are available.

Each level facility places more load on the implementation, though except for very high message volumes, probably even the message tracker will not slow things down appreciably.

Some more discussion and screenshots of these features are given below. For detailed information on exactly what components and models are available in each case, consult the relevant javadocs.

The client list keeps track of the clients currently registered and their associated metadata and subscriptions. Some ready-to-use components which display this information are available; the following HubMonitor screenshot shows these:

HubMonitor screenshot

The upper left part is a JList of clients with a panel showing the metadata and subscriptions for a selected client on the right. The icon panel in the lower left part gives a more compact display of the currently registered clients. In both cases these components automatically update themselves as clients register and unregister. There is also a register/unregister toggle button and connection status icon at the bottom right.

You can also obtain the ListModel which contains the Client objects for your own custom use if you prefer not to use these components as provided.

The message tracker classes as well as showing the registered clients and their metadata and subscriptions, also keep track of which clients have sent messages to which others, and whether successful responses or other outcomes have resulted. In the case of the hub, all messages from any client to any other can be seen; for the client classes, only those messages sent/received by the client using those classes are visible, because of the SAMP architecture.

The message tracker windows show a tabbed panel. One tab shows the client list as for the earlier example, with the change that a representation of what messages are currently (or recently) in progress is shown after each client's name: little triangles indicate messages received from/sent to to each client (according to whether they are on the left/right of the circle). The triangles change colour etc according to the status of the message in question - hovering the mouse over them gives a tooltip with some more information. You can also obtain a small standalone component which contains just this graphical panel giving message status. This is what the message tracker hub GUI client tab looks like:

Message tracker hub client list

Another tab shows a window which gives more detail on messages sent/received. For each message, a summary row is given in a table, and more detail is shown in a panel below, including the complete message and response contents. The messages are retained in the table for a little while after they have completed to allow them to be examined, and then disappear automatically. The message detail tab for the hub looks like this:

Message tracker hub message list

You can also obtain the various ListModels containing message Transmission information if you want to construct your own custom components based on these.

jsamp-jsamp-1.3.7/src/site/xdoc/history.xml000066400000000000000000001152331356450004300206640ustar00rootroot00000000000000 Change Log Mark Taylor

Date: 31 Jul 2008

Initial release (beta). Targets SAMP WD v1.0 (25 June 2008).

Date: 19 Sep 2008

Code reorganisation and pluggable XML-RPC implementation:
The XML-RPC and Standard Profile-related parts of the library have been reorganised somewhat. A new package org.astrogrid.samp.xmlrpc ant its descendants now hold all the code which relates specifically to XML-RPC communications and the SAMP Standard Profile; the code in the other packages is profile-agnostic and handles only transport-independent aspects of the protocol. The xmlrpc package itself defines a pluggable interface for providing XML-RPC client and server functionality; two implementations are also provided, in the packages xmlrpc.apache and xmlrpc.internal. The Apache one is basically what was there in previous versions; the internal one is completely freestanding, and if this is used the Apache XML-RPC library is not required for operation.
GUI functionality added
There are more classes in the org.astrogrid.samp.gui package to facilitate high-level use of SAMP within GUI applications. ConnectorGui provides Actions suitable for insertion in a general SAMP control menu, and SendActionManager provides menus and Actions for sending particular messages.
New command snooper:
Logs received messages to the terminal. Useful for debugging.
Minor changes:
  • Moved some GUI functionality from client.HubConnector to gui.ConnectorGui.
  • HubClient privateKey member is now an Object not a String, for greater generality.
  • RegInfo(String,String,String) constructor withdrawn.
  • HubRunner class moved from package hub to xmlrpc.
  • Several other more or less minor changes.

Date: 25 Sep 2008

Bugfixes:
  • Fixed a problem with XML-RPC implementation when running in presence of unexpected (not 1.2) version of Apache XML-RPC library.
  • jsamp.xmlrpc.impl property now correctly propagated to JVM running external hub.
  • Race condition in unit tests which sometimes gave false failures is fixed.
  • Fixed threading problem in hubmonitor which meant that sometimes window did not appear.
  • Some other minor items.

Date: 9 Dec 2008

Major New Features:
  • There is now an option for both hub and client GUI to keep track of and display messages which have been sent. HubRunner and HubMonitor GUIs by default now have tabs showing messages sent/received and their current status - see GUI Features section for some screenshots. To see this in action, start the HubRunner in (default) msg-gui mode and then run HubTester. Or just use the hub with your own SAMP clients. Note that this functionality incurs some overhead - if not used no such overhead is incurred.
  • A new small GUI component is available which just shows the icons for all registered clients (like what used to appear in the bottom right corner of VODesktop windows).
  • Facilities have been added for full logging of all XML or RPC communications in the hub or clients. See new xml-log and rpc-log options to -xmlrpc flags of command-line tools, and classes org.astrogrid.samp.xmlrpc.internal.*LoggingInternal*.
  • Replace DefaultSendActionManager with other, more capable, SendActionManager subclasses. This makes it easy to handle the results from messages which have been sent, for instance by passing the information to the user graphically or in other ways.
  • HubMonitor tool can now subscribe to messages other than administrative ones, like Snooper.
  • MessageSender tool now pretty-prints response(s).
Backwards Compatibility:
There have been a number of changes since release 0.2 which are backwardly incompatible, so that source code using earlier versions of JSAMP will need to be adjusted. GUI-related functionality is most affected. This is because I'm still designing it, and some changes were required to accommodate new features etc; sorry. I hope that there will be fewer backwardly incompatible changes in the future as the library matures - but I'm not offering any guarantees just yet. Some of the more obvious changes are as follows:
  • New class gui.GuiHubConnector now contains the Swing-related functionality which was previously in (its superclass) client.HubConnector, and also all the functionality from the now removed class gui.ConnectorGui.
  • HubRunner and ConnectorGui APIs modified to permit use of various different hub implementations (with different graphical characteristics - see HubMode).
If there are things which used to work in a previous version and you can't see how to do it now, please contact me and I'll advise how to update.
Bugfixes:
  • Fixed server error which sometimes resulted in failed reads, especially for long messages.
  • Fixed error in handling CalcStorm -xmlrpc flag.
Other:
  • Change return values of callAll and notifyAll hub methods as per agreed modifications to the standard at version 1.1. Affects hub and client API, implementation and test suite.
  • Default XML-RPC implementation is now Internal rather than Apache.
  • HubConnector now subscribes to samp.hub.disconnect MType.
  • Improved performance of internal client when sending long messages.
  • Add SampUtils.isSampChar() convenience method.
  • Add public createTag method to HubConnector.
  • Added test for long messages in HubTester.
  • HubTester now tests nearly all legal SAMP string characters.
  • ...And some other things not mentioned here.

Date: 27 Mar 2009

Minor Enhancements:
  • Add GuiHubConnector.createRegisterOrHubAction method, which registers if it can, else offers the user to start a hub. This may be the only button you need.
  • Internal HTTP server now tolerates LF as well as the correct CRLF as HTTP request header line terminators, as recommended by HTTP 1.1 (RFC2616).
  • Add convenience methods call and callAll to HubConnector - these allow you to make asynchronous calls so that the results are delivered as callbacks to supplied objects without having to worry about registering handlers and matching tags.
  • Add createTargetSelector/createTargetAction methods to SendActionManager class. These give another way (button plus combo box) go allow users to trigger a send.
  • Add comment to .samp lockfile noting that contact XML-RPC URL hostname is configurable.
  • Cache home directory when first determined in SampUtil.
Backward Compatibility:
  • Moved HTTP server implementation used by internal XML-RPC implementation into its own package, samp.httpd. Also added some utility classes in that package to facilitate serving dynamic resources and resources from the classpath. This is because having a simple self-contained HTTP server may be useful for client implementations doing SAMP-like things other than strictly communicating with the hub.
  • ResultHandler and LogResultHandler classes moved from package gui to client.
  • Changed SAMP protocol version to 1.11.
Bug Fixes:
  • Fixed hub lockfile location bug for Windows at Java 1.4.
  • Fix process handling bug which caused Windows XP external hub start to write incomplete lock file.

Date: 5 Aug 2009

Though this version is numbered 1.0, it's not a giant leap ahead of the previous one (0.3-1). However, this is the first release since SAMP became an IVOA Recommendation, and this toolkit is believed to be fully compliant with that standard. The intention is that backwardly incompatible changes will be kept to a minimum following this release.

New functionality:

  • New Bridge client added. This is a significant bit of infrastructure which allows clients on different hubs to interoperate.
  • Added popup menus to GUI hub views which allow you to ping or forcibly disconnect registered clients.
  • System property jsamp.server.port provided to allow selection of the default server port.
  • System property jsamp.lockfile provided to support non-standard lockfile location.
  • HubConnector now subscribes by default to new client.env.get MType.
  • Added -clientname and -clientmeta flags to Snooper command-line tool.
  • You can now set the samp.secret string for HubRunner if you don't want it chosen randomly.
  • Add some more documentation pages: System Properties, Debugging Aids and Bridge.
  • The help message now reports relevant system properties as well as other help info.

Changes to behaviour (note some of these may have backward compatibility issues):

  • The default hostname for HTTP server etc (SampUtils.getLocalHost()) is now "127.0.0.1", not the DNS name; in certain network environments this works better than the alternatives, though it's less good for inter-machine communications. This default can be altered by setting the samp.hostname system property; it has two new special values "[hostname]" and "[hostnumber]".
  • Icon URLs declared by test clients etc now use internal server references rather than links to external static resources. This means icons are not dependent on network availability.
  • Clients forcibly disconnected by samp.hub.disconnect now don't try to re-autoconnect themselves.
  • Remove warning if permission change on .samp file to owner-only read fails. This permission change is probably not possible on Windows-like OSes (unless anyone can tell me different), and the warning causes confusion.
  • System property samp.localhost renamed to jsamp.localhost (the old name is still recognised for backward compatibility).
  • Withdraw -xmlrpc flag from command-line tools; the jsamp.xmlrpc.impl system property should be used instead.
  • Standard version is now reported as v1.11 REC 2009-04-21.

API Changes (note some of these may have backward compatibility issues):

  • Added DefaultClientProfile class; this is now the recommended way of getting a general purpose ClientProfile object (rather than using StandardClientProfile.getInstance(). Using this will aid pluggability (ability to use with non-standard profiles in the future).
  • Added UtilServer class; this can help to reduce the number of HTTP servers run by a JSAMP application, and provides convenience methods for exporting local (e.g. file: or classpath) URLs.
  • Added getHubClient and disconnect methods to BasicHubService; you can now use the hub object to forcibly disconnect clients.
  • StandardClientProfile now has overridable getLockInfo method for better customisability.
  • Added parseValue utility method to SampUtils class.
  • Added new method HubConnector.connectionChanged.
  • InternalServerFactory now returns a server which can be safely reused.
  • Class LockInfo moved from org.astrogrid.samp package to org.astrogrid.samp.xmlrpc, which is where it should have been.

Bugfixes:

  • Missing jsamp.version file now included in source zip archive.
  • Fixed error reporting bug in messagesender tool.

Date: 21 Jul 2010

Protocol extension:

  • The environment variable SAMP_HUB can now be used to specify a non-default lockfile location for clients and hub to use with the Standard Profile. This usage is expected to be part of the next version of the SAMP standard (SAMP 1.2, in WD at time of writing). See the documentation for the StandardClientProfile class for details, and DefaultClientProfile for a JSAMP-specific extension to this mechanism. The non-standard jsamp.lockfile and jsamp.profile system properties which did the same job in a non-standard way have been withdrawn. This has some backwardly incompatible consequences:
    • jsamp.lockfile and jsamp.profile system properties withdrawn; use SAMP_HUB environment variable instead.
    • SampUtils.LOCKFILE_NAME constant withdrawn; use StandardClientProfile.LOCKFILE_NAME instead.
    • SampUtils.getLockFile() method withdrawn; use StandardClientProfile.getLockUrl() instead. If you want to find out if a hub is running, instead of SampUtils.getLockFile().exists(), use DefaultClientProfile.getProfile().isHubRunning().
    • LockInfo.readLockFile(File) method withdrawn; use LockInfo.readLockFile(URL) instead.
    • LockInfo.readLockFile() method withdrawn; use StandardClientProfile.getLockInfo() instead.
    • LockWriter no-arg constructor withdrawn.

Enhancements:

  • Where possible, if the hub is running in GUI mode it will now install itself as an icon in the "system tray" rather than posting the window directly; a popup menu associated with the tray icon allows window display and hub shutdown. If the platform does not provide system tray functionality, it will revert to the previous behaviour of posting the window directly. System tray functionality is available only when running under Java 1.6 or later, and only when using a suitable display manager.
  • Added -httplock flag to hubrunner, which allows publication of lockfile by HTTP for use by non-localhost clients.

Usability:

  • Invoking JSamp class (e.g. "java -jar jsamp.jar") with no arguments now starts the hub rather than just printing a usage message.
  • ClientProfile interface has new method isHubRunning. This is more general (and easier to use) than testing something like StandardClientProfile.getLockFile().exists().
  • HubRunnner.runHub method now returns the running HubRunner instance.

Bugfixes:

  • Fix error in XML-RPC fault responses (faultCode was string instead of int).
  • Fix the MessageTracker hub so it doesn't get confused if clients re-use the same tag for different messages.
  • Adjusted logic of client tracking in HubConnector to be more robust.
  • Fix bug in outgoing message tooltips for MessageTracker message box.
  • Fix concurrency issue in HttpServer.
  • Fix issue which could cause GUI freeze while waiting for remote icons to load.
  • Catch security exceptions when calling addShutdownHook in XmlRpcHubConnection. It seems that signed applets may not have the appropiate permissions.

Minor improvements:

  • Command-line tools now use DefaultClientProfile.getProfile() rather than StandardClientProfile.getInstance() as they should (hence will be correctly influenced by SAMP_HUB environment variable).
  • More tests added to HubTester for correctness of XML-RPC responses.
  • Be more frugal with DocumentBuilderFactory instances to reduce some unwanted logging.
  • Improve wait implementation in HubTester to avoid non-yield race conditions.
  • A warning is now issued if getClientMap is called before declareSubscriptions on a HubConnector, since without declaring subscriptions the client map won't work.

Date: 15 Feb 2011

The two main changes at this release are generalisation of the hub running framework to allow multiple profiles to run interfacing to the same hub simultaneously, and implementation of the experimental Web Profile. The former was motivated by the latter (though should really have been present from the start). This work was suggested by Jonathan Fay and financially supported by Microsoft Research, whose support is gratefully acknowledged. There are also a number of bug fixes and minor enhancements.

Hub framework:

The org.astrogrid.samp.hub.HubRunner class has been deprecated in favour of the new class org.astrogrid.samp.hub.Hub. This can be used from the command line or programmatically to start a hub, and it can run zero or more profiles, defined by the new HubProfile interface, simultaneously. HubProfile implementations are provided for the Standard Profile and the Web Profile, and you can plug in your own at runtime. This class is now the Main-Class defined in the jar file's Manifest, so invoking (e.g. clicking or java -jar) the jsamp-*.jar file will now invoke this class. Documentation of the command-line usage is on the Command-line Tools page. By default only the Standard Profile is run, so simply invoking the jar file will have much the same behaviour as it did in previous versions, that is starting a Standard Profile-only hub. Which profiles are run can be influenced in various ways, including by defining the jsamp.hub.profiles system property.

A new "facade" mode of hub operation has been introduced, which allows tunnelling from one hub profile to another (mostly of interest to hub profile implementors).

There have been a number of other backwardly incompatible changes to the hub implementation classes: Most of the HubService interface has been replaced using HubConnection and Receiver has been replaced with CallableClient, reducing amount and duplication of code, and some assumptions specific to the Standard Profile have been removed from the interfaces of hub classes which are properly profile-independent. These changes are not believed likely to affect anybody who is not writing hub implementation code.

Web Profile:

This release also includes client and hub implementations of the experimental Web Profile. Implementation is in the new package org.astrogrid.samp.web. Note that this profile is still under discussion and details of the definition may change in the future.

Bug fixes:

  • Fixed URI escaping bugs related to "+" characters (new utility methods SampUtils.uriEncode/Decode).
  • Fixed some concurrency bugs to do with handler lists in HubConnector, HttpServer, InternalServer and ApacheServer (thanks to Laurent Bourgès).
  • Fixed, I think, threading issues that occasionally prevented hub forced shutdown notifications getting to some clients. It is possible this fix will have knock-on performance or other effects, especially in the presence of badly-behaved clients - please report if you notice problems. Thanks (again) to Laurent Borgès for extensive help with this.
  • Fixed a bug related to output capture when an external hub is started.
  • Fixed some not-quite-correct internal HTTP server error responses (405, 400).
  • Fixed internal HTTP server so it won't reorder request/response headers.
  • Improved header handling (case-sensitive, duplicated keys) in HttpServer.
  • Fixed client XML logging bug.

Minor changes and enhancements:

  • Changed pluggable XML-RPC implementation framework API slightly - handleCall method in SampXmlRpcHandler interface gets an additional argument.
  • SAMP Version now declares itself as SAMP v1.2 REC.
  • Some additional system properties are now propagated when starting an external hub.
  • Window positioning now follows platform norms by default rather than the java policy of placement at (0,0). Specifically: the java.awt.Window.locationByPlatform system property is set, if it does not already have an explicit value, in the JSamp class.
  • AbstractMessageHandler has a new method createResponse which may be overridden for more flexibility.
  • External hub start action is disabled in JNLP context, since it won't work.

Date: 2 Aug 2011

Various changes relating to configurable use of profiles and the Web Profile in particular, to match the SAMP 1.3 WD, and to enable experimentation with multi-profile configurations while we gain experience with security options. There is more discussion in the new Profiles page, but the main changes are:

  • The Web Profile is now available, but not switched on, by default. The Web Profile (and other profiles) can be switched on or off while the hub is running by using the new Profiles menu in the hub window or system tray icon. There is a corresponding new hub command flag -web:extraprofiles and system property jsamp.hub.profiles.extra.
  • The Profiles menu also has a Web Profile Configuration submenu, which allows fine control over the way the Web Profile is run. This is to permit experimentation with security options; this menu or some of its options may be withdrawn in future versions. There are corresponding flags for the hub command: -web:[no]cors, -web:[no]flash, -web:[no]silverlight, -web:[no]urlcontrol.
  • The Web Profile hub server now blocks non-localhost requests at the HTTP level. This should benefit security.
  • The Web Profile URL translation service now by default blocks access to sensitive resources (local filesystem and localhost URLs) if they have not previously been mentioned by a non-Web Profile client. This should benefit security.
  • Web profile now uses a map argument and not a string argument to register(), as per the change to the Web Profile specification.
  • Various related changes to the hub implementation and API have been made to enable the above. The recommended entry point, Hub.runHub(), still works though.

The Hub GUI window now has menus:

  • File menu, which just allows hub shutdown.
  • Clients menu, which allows pinging and disconnection of clients (this functionality duplicates the existing, but not very obvious, client popup menu).
  • Profiles menu, as described above.

JSON is now used as the standard serialization/deserialization format for SAMP objects in a few places:

  • New utility methods toJson and fromJson in SampUtils class.
  • Non-scalar message parameter contents and client metadata can now be specified to the messagesender and snooper tools on the command line using JSON syntax.
  • Message display, e.g. in the hub message tracker GUI, are now shown in JSON format. In practice this means you'll see a few more double quotes - it also means they can be cut and pasted directly into other JSON-aware contexts.

Other items:

  • The ClientProfile.isHubRunning method now probes more agressively for a hub (for instance in the Standard Profile it pings rather than just looking for the .samp file). This is a change to both the API and the implementations.
  • The Web Profile registration dialogue is now somewhat internationalised. Thanks to Luigi Paioro and Markus Demleitner for providing translations. This can be extended by providing further instances of AuthResourceBundle class or supplying a suitable org.astrogrid.samp.web.AuthResourceBundle_xx.properties resource; run AuthResourceBundle.main() for example output.
  • The Web Profile hub now fails at start time rather than during registration attempts if Swing client authorization is in use with headless graphics.
  • More helpful warning message when standard profile lockfile is present but broken (suggests deleting file).
  • HubMonitor now correctly uses DefaultClientProfile rather than StandardClientProfile.

Date: 26 Oct 2011

  • Hub now runs with Web Profile on by default.
  • Web Profile has a new configuration option, which restricts permitted MTypes that a Web Profile client can send. This is switched on by default.
  • Tone down warning in the Web SAMP confirmation dialogue slightly to reflect improved security measures in default Web Profile configuration.
  • The messagesender command-line tool has new flags -targetname and -targetid which replace the previous -target flag.
  • Add French translation for Web SAMP confirmation dialogue (thanks to Thomas Boch).
  • URL encode argument of ResourceHandler.addResource (thanks to Omar Laurino).
  • Add -web:auth extreme mode to hub.
  • Web hub profile now throws SampException not SecurityException on registration failure; this makes failures less verbose in the hub.

Date: 12 Dec 2011

  • Remove some code which was not compatible with J2S1.4.
  • Web Profile URL control now controls all URLs not just local ones. All maps passing through the hub connection are now scanned for URLs.
  • Reduce logging for message send failures, partly by reducing failures themselves.
  • Hub implements x-samp.query.by-meta MType.
  • New methods on Hub class to determine whether the hub is running and with what profiles.
  • Added -subs flag to Snooper tool.
  • Fixed bug in JSON parser (wouldn't recognise empty objects "{}").
  • The implementation of MType restrictions for Web Profile clients has been changed. Blocked MTypes are now completely hidden from Web Profile clients. There is still a list of blocked MTypes (ones with known harmless semantics), but subscribing clients may override this, i.e. note MTypes that they subscribe to as known to be either harmless or harmful by annotating the subscription (populating the map value corresponding to the MType key) with a key "x-samp.mostly-harmless" and a value of "1" (harmless) or "0" (harmful).
  • The Web Profile registration dialogue now reports the apparent URL of the registering client (as determined from the Referer header) if available.

Date: 30 Oct 2012

  • Failure to start some profiles no longer prevents the hub from starting up at all. This means that default configuration (Standard + Web profile) hub startup no longer fails on a machine that already has a web profile hub running on it.
  • Reported standard version is now 1.3 REC.
  • Fixed some broken command-line hub options (-[no]restrictmtypes and -[no]urlcontrol).
  • Command line -web:* and -std:* flags are now used even when -profiles flag is not supplied explicitly (where possible).
  • Added Hub.getRunningHubs static method to locate hubs running in current JVM, as requested by JMMC.
  • Added Hub.getWindow() method, as requested by JMMC.
  • Improved shutdown sequence, shutting down clients first and then hubs, following contribution from JMMC.
  • Plugged several resource leaks (cancel auxiliary threads in HubConnector on setActive(false), free XML-RPC servers associated with callable clients on unregister) following contribution from JMMC.
  • The distributed jar file is now signed with a Thawte code-signing certificate (owned by the University of Bristol). This means that you can provide a JNLP file to start a hub using WebStart which will not complain to the user about not being able to authenticate the publisher's identity.

Date: 9 Oct 2013

  • JSAMP has been rehosted, and no longer lives on AstroGrid sites. The distribution web site is now at http://www.star.bristol.ac.uk/~mbt/jsamp/, and the source can be found on github at https://github.com/mbtaylor/jsamp.
  • Fixed the Web Profile hub to work with HTTPS-based web pages as well as HTTP-based ones; previously HTTPS pages were blocked for no good reason(?). Developers of applications which may run the JSAMP hub are encouraged to upgrade to this version to avoid penalising use of HTTPS in third-party SAMP web applications. Note added later: the HTTPS problem is not so easily solved.
  • Add new system property jsamp.web.extrahosts to allow trusted non-local hosts to register with the Web Profile (for instance near-local mobile devices).
  • Improve shutdown behaviour somewhat, now less prone to fail to delete .samp file in JNLP shutdown.
  • The jar file is now distributed in both signed and unsigned forms.

Date: 13 Nov 2014

  • The build is now done using Maven 3. Thanks to Paul Harrison for doing most of the work on this. As a consequence, the version numbering system has changed - it's now a.b.c instead of a.b-c. Note although the POM file is updated etc, the release is not currently deploying to a central maven repository; I haven't worked out how to do that yet. If somebody out there is keen to see that happen and wants to help me out, get in touch.
  • The jar file now contains "Permissions" and "Application-Name" attributes, which are required when running the hub as a signed jar under WebStart at java 7u51+.
  • The Web Profile security dialogue has been adjusted so that long strings transmitted as application Name, Origin or URL do not extend the width of the popup window indefinitely.
  • Updated Standard Profile samp.profile.version value to 1.3. By an oversight it had got stuck at 1.11.
  • The hub now completes client calls without waiting for HTTP responses. (XmlRpcCallableClient implementation uses callAndForget instead of callAndWait). This prevents some cases of calls hanging in the presence of badly-behaved clients. Some other bugfixes related to hub shutdown were required to prevent this causing knock-on problems.
  • Work round a JNLP shutdown classloading bug.
  • One or two minor bugfixes.

Date: 19 Sep 2019

  • New command SampLoad, intended for use as browser helper application.
  • Deprecate UtilServer.exportResource(String) in favour of exportResource(URL), on the grounds that it's easy to end up using the wrong classloader.
  • Fix a javadoc error in SelectiveClientListModel.
  • Fix a bug in SendActionManager that meant the targeted send action didn't do anything.
  • Add HubConnector.isActive method.
  • Downgrade internal HTTP server 404 logging messages from WARNING to INFO, so they are generally no longer visible to users.
  • Fix SampUtil.fileToUrl to cope with non-URL-safe characters in filenames (UTF-8 encoding is used).
  • Various changes to the code to facilitate implementation of the (experimental, not included here) TLS profile. These mostly involve publicising previously private methods, but there are a few changes to the client authorization for the Web Profile, including a backwardly-incompatible change to signature of the web.ClientAuthorizer.authorize method. Library users are not expected to notice these changes.

Date: 18 Nov 2019

  • Relax Origin restrictions in CORS web server to allow e.g. browser extensions.
  • jsamp.notray system property introduced to prevent the hub appearing in the System Tray.
jsamp-jsamp-1.3.7/src/site/xdoc/index.xml000066400000000000000000000061061356450004300202700ustar00rootroot00000000000000 JSAMP Mark Taylor

JSAMP is a Java toolkit for use with the Simple Applications Messaging Protocol. It provides the following components:

  • A SAMP hub implementation, suitable for standalone or embedded use
  • A set of classes which can be used to implement SAMP capabilities in client applications
  • A hub test and benchmarking suite, which can test correctness and performance of third-party hub implementations
  • Utilities which can be used by client and hub developers, or SAMP users, to analyse or debug SAMP sessions
  • Graphical components built on top of the basic functionality suitable for use with the above
  • Hub and Client implementations of the Standard and Web Profiles.
  • A bridge component, which can allow clients on different hubs to talk to each other

For a given purpose, some or all of these components may be used at one time.

The toolkit's design aims are, in rough order of priority:

  • Correctness and completeness according to the SAMP standard
  • Robustness
  • Ease of use for client application authors
  • Ease of deployment within third-party applications (few dependencies)
  • Flexibility, including the possibility to use with custom SAMP Profiles
  • Good documentation
  • Small size (this one's gone by the board a bit)

The current version targets the SAMP 1.3 Recommendation document (11 April 2012).

JSAMP is currently developed by Mark Taylor (m.b.taylor@bristol.ac.uk), working in the Astrophysics Group at Bristol University. It was originally developed within the UK's AstroGrid project, and has also been supported by STFC grants ST/H008470/1, ST/I00176X/1 and ST/J001414/1, and by Microsoft Research. All of this support is gratefully acknowledged. Many members of the SAMP community have provided valuable input and help with development; special mentions go to Thomas Boch, Laurent Bourgès, Sylvain Lafrasse, Jonathan Fay, Paul Harrison, Luigi Paioro, John Taylor and Ivan Zolotukhin.

Queries, bug reports, suggestions should generally be mailed to the author. Alternatively they can be addressed at the source respository or, if of general interest raised on the apps-samp@ivoa.net mailing list.

JSAMP is open source software; it is available under (at least) the Academic Free Licence and the BSD Licence.

jsamp-jsamp-1.3.7/src/site/xdoc/profiles.xml000066400000000000000000000233631356450004300210100ustar00rootroot00000000000000 Profiles Mark Taylor

The SAMP protocol is defined in two parts, as an abstract API and as transport-specific Profiles. One or more Profiles may be offered by a SAMP hub to allow clients to communicate with it. At present, JSAMP offers two basic profiles, the Standard Profile, intended for normal desktop-based clients, and the Web Profile, intended for browser-based clients (some variants of these are also possible). These are described below.

By default, the hub is configured with both Standard and Web profiles running. You can turn them on and off while the hub is running using the Profiles menu. You can configure which profiles are run on startup using the -profiles and -extraprofiles hub flags or the jsamp.hub.profiles and jsamp.hub.profiles.extra system properties, or if you're writing code with one of the methods in the Hub class.

The Profiles menu in the hub window looks something like this:

Screenshot of Profiles menu in the hub GUI window

and from the system tray icon something like this:

Screenshot of Profiles menu in the system tray window

Checking one of the checkboxes has the effect of turning the profile in question on, and unchecking it turns it off. When a profile is turned off, any clients registered using that profile are forcibly ejected from the hub.

The Standard Profile is intended for use by normal desktop tools. Clients discover the location of the hub by looking in a file named .samp in the user's home directory. The fact that this file is normally only readable by the user running the hub means that connections cannot be made by other users.

The Web Profile is intended for use by web applications, that is, programs or web pages running inside a web browser. Web applications can find the hub at a well-known port. When a web application wants to register, the hub will ask the user, by popping up a dialogue window, whether the application should be allowed to run. The dialogue window will look something like this:

Web Profile popup dialogue screenshot

There are a number of configuration options available for the Web Profile hub, connected with security. They may be set on the hub command line, with the various -web:* options, or using the Profiles|Web Profile Configuration menu. The options are as follows:

CORS cross-domain access:
Whether to allow access using the Cross-Origin Resource Sharing standard. this is believed to be the safest mode of browser/hub communication, so it should usually be switched on.
Flash cross-domain access:
Whether to allow access using the Adobe Flash-based crossdomain.xml mechanism. This may be less secure than CORS, but is required for some browser/web application combinations. If you use a browser that supports CORS (thought to be: Chrome v2.0+, Firefox v3.5+, Safari v4.0+, IE v8+), and only use JavaScript-based web SAMP applications, you may be able to leave this switched off and thereby improve security.
Silverlight cross-domain access
Whether to allow access using the Microsoft Silverlight-based clientaccesspolicy.xml mechanism. Silverlight is believed to support the Flash mechanism, so you can and should probably leave this switched off.
URL Controls:
Whether web clients are restricted from accessing sensitive resources, like local files, if they have not previously been mentioned in earlier SAMP messages. This option is experimental, but it is probably a good idea to leave it on for security reasons.
MType Restrictions:
Whether the messages web clients are permitted to send are restricted. If this option is selected, only MTypes matching a well-known list of harmless ones are permitted - this includes all the common MTypes to exchange tables, images, spectra etc. This option is experimental, and you may need to switch it off if a web client has to send messages with unusual MTypes, but otherwise if is a good idea to leave it on for security reasons.

Note that the configuration options may only be changed when the Web Profile itself is not running.

You may be able to find some web profile clients in the sampjs project.

The JSAMP 1.3 Recommendation discusses security in relation to the Web Profile, but notes that there remain some security concerns, and that experimentation will continue in hub implementations around this issue.

The security measures taken by the JSAMP Web Profile implementation relating to the Web Profile are:

  • The HTTP server on which the Web Profile runs will normally reject any access attempts from hosts other than the local host, as recommended by the SAMP 1.3 document. However, access by additional trusted hosts may be enabled if they are explicitly named using the jsamp.web.extrahosts system property.
  • The Web Profile URL translation service (Section 5.2.6 of SAMP 1.3) is, by default, selective about what URLs it will proxy. URL translation is only performed for a URL which has previously been mentioned (for instance as the value of a Message or Response argument or a declared Metadata map) in a SAMP communication from a trusted (non-Web Profile) client. Translation requests which do not meet this criterion are met witha 403 Forbidden response. This means for instance that a Web Profile client cannot simply request, e.g., the content of file:///etc/passwd. This policy is on by default, but can be switched off and on using the Profiles|Web Profile Configuration|URL Controls menu item from the hub GUI, or with the -web:[no]urlcontrol hub command-line switch.
  • The MTypes which a web client is permitted to send is, by default, restricted. In this way, web clients can be restricted to sending messages known to have harmless semantics, and blocked, for instance, from sending messages which cause scripts to be executed on desktop clients, which have more potential for dangerous effects. By default, only MTypes matching a "whitelist" of wildcards are allowed; this includes samp.app.*, table.*, image.*, and others which are used for the normal exchange of data. This restriction can be switched off and on using the Profiles|Web Profile Configuration|MType Restrictions menu item from the hub GUI, or with the -web:[no]restrictmtypes hub command-line switch. There are ways from both the hub and the (non-Web) client side of achieving finer control. In particular non-Web clients may annotate their MType subscriptions list using the key "x-samp.mostly-harmless"; setting this to 1 or 0 adds the MType to or removes it from the whitelist for that particular subscribing client. See the documentation of the ListMessageRestriction class for more detail.
  • The Web Profile may be switched off. Users can switch it on and off during hub operation using the Profiles menu from the hub GUI (if present). Since version 1.3-1 it is switched on in the default configuration.
  • If the Web Profile is switched off during operation using the Profiles|Web Profile menu item in the hub GUI, any clients registered through the Web Profile will be disconnected immediately. A user can therefore eject Web Profile clients if there are suspicions about their behaviour. Single clients can be ejected as well using the Clients|Disconnect Selected Client menu item.
  • The details of which cross-domain workarounds are used can be controlled from the Profiles|Web Profile Configuration menu or from the hub command line. The different workarounds can be switched on and off independently, though only while the Web Profile is not running. By default CORS and Flash are switched on, and Silverlight is switched off.

The upshot of all this is that in the default configuration, even if a hostile web application connects to the hub, it is most unlikely to be able to do anything worse than, for instance, send unwanted FITS images to your image viewer. If such a hostile client is oberved, it can be forcibly disconnected, either individually or by shutting down the Web Profile (or the Hub itself). Such a hostile (or indeed friendly) client can of course only ever connect to the Hub following explicit authorisation by the user (clicking "Yes" on the registration popup dialogue).

jsamp-jsamp-1.3.7/src/site/xdoc/sysprops.xml000066400000000000000000000206571356450004300210720ustar00rootroot00000000000000 System Properties Mark Taylor

System properties are a way of communicating values from the runtime environment to Java; they are effectively Java's answer to environment variables. JSAMP defines some system properties which you can set to alter the way that it behaves.

If you are starting java from the command line, you can specify these using java's -D flag with the syntax -Dname=value. Note that any such flags must occur before a -jar flag on the command line. For example, to request that JSAMP opens its default HTTP server to listen on port 2112, you could invoke a JSAMP-using application like this:

   java -Djsamp.server.port=2112 -jar foo.jar

They will work the same way for JSAMP's command line tools, and for third-party applications which use the JSAMP library for SAMP communications.

System properties can also be set programmatically from within Java code. Note that in this case you may need to set these property values near the start of the application; in most cases they are only read once.

The following system properties may be used to affect JSAMP's behaviour. Listed alongside the property name is a link to the static public class member which may be used to refer to this property name in code: the javadocs so linked may provide more detail on use.

jsamp.hub.profiles (Hub.HUBPROFILES_PROP):
jsamp.hub.profiles.extra (Hub.EXTRAHUBPROFILES_PROP):
These define the default profiles which a hub will run. If a hub is started from within JSAMP without specifying which profiles it will use, the profiles are defined by the value of these properties. profiles determines the profiles which will start when the hub starts, and profiles.extra determines additional profiles which may be started later under user control. The values are comma-separated lists, and each item may be one of:
  • std: Standard Profile
  • web: Web Profile
  • hubprofile-classname: the name of a class which implements HubProfile and has a suitable no-arg constructor.
If this property is not specified, a default list will be used. This is currently the Standard and Web Profiles on start, with no extras (equivalent to jsamp.hub.profiles=std,web, jsamp.hub.profiles.extra=""). This property only affects how a hub is run; it has no effect on SAMP clients.
jsamp.localhost (SampUtils.LOCALHOST_PROP):
Sets the hostname by which the local host is to be identified in URLs, for instance server endpoints. If unset, the default is currently the loopback address 127.0.0.1. However, if this property is set (presumably to the local host's fully- or partly-qualified domain name) its value will be used instead. There are two special values:
  • [hostname]: uses the host's fully qualified domain name
  • [hostnumber]: uses the host's IP number
jsamp.notray (SysTray.NOTRAY_PROP):
Prevents the hub appearing in the System Tray if set "true". Normally when the hub runs it sits in the system tray on platforms that support a system tray, and otherwise it appears in a window on the desktop. But if this property is set, the system tray is never used.
jsamp.server.port (UtilServer.PORT_PROP):
Gives a preferred port number on which to open the default server. In most cases the default server is the only HTTP server used by an application using JSAMP, though they can have more than one. If this property is undefined or set to zero, or if the specified port number is already occupied, an unused port is chosen by the system.
jsamp.web.extrahosts (CorsHttpServer.EXTRAHOSTS_PROP):
Gives a comma-separated list of names (host names or IP numbers) of hosts that are permitted to use the Web Profile alongside the localhost. Normally web profile access is only allowed to the local host for security reasons, but trusted "near-local" hosts may be added here if required. One possibility is to add the address of a mobile device to be used for external application control.
jsamp.xmlrpc.impl (XmlRpcKit.IMPL_PROP):
Indicates which pluggable XML-RPC implementation should be used. If defined, this may be one of the following strings:
  • internal: normal internal implementation
  • xml-log: internal implementation which logs all incoming and outgoing XML-RPC messages by writing their full XML form to standard output
  • rpc-log: internal implementation which logs all incoming and outgoing XML-RPC messages by writing an abbreviated form of their content to standard output
  • apache: implementation using Apache's XML-RPC library version 1.2; this requires the Apache xmlrpc-1.2b classes to be on the classpath
The members of this list are given as the contents of the XmlRpcKit.KNOWN_IMPLS array. Alternatively the full classname of a class which implements org.astrogrid.samp.xmlrpc.XmlRpcKit and which has a no-arg constructor may be given. The default is currently internal if this property is not specified. The implementations ought to behave the same as far as communications go, though there may be performance differences (the logging ones will be slower for sure). The logging implementations can be useful for debugging.

Note that the system properties jsamp.lockfile and jsamp.profile, which existed in JSAMP 1.0, have been withdrawn in subsequent versions. Use the SAMP_HUB environment variable, in accordance with the standard profile extension, instead.

jsamp-jsamp-1.3.7/src/test/000077500000000000000000000000001356450004300155125ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/000077500000000000000000000000001356450004300164335ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/org/000077500000000000000000000000001356450004300172225ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/000077500000000000000000000000001356450004300212205ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/000077500000000000000000000000001356450004300221605ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/CommandTest.java000066400000000000000000000012011356450004300252330ustar00rootroot00000000000000package org.astrogrid.samp; import junit.framework.TestCase; public class CommandTest extends TestCase { public void testCommands() throws Exception { String[] cmdClasses = JSamp.COMMAND_CLASSES; for ( int i = 0; i < cmdClasses.length; i++ ) { String className = cmdClasses[ i ]; Class clazz = Class.forName( className ); assertNotNull( JSamp.getMainMethod( clazz ) ); } } public void testVersion() { assertTrue( SampUtils.getSampVersion().trim().charAt( 0 ) != '?' ); assertTrue( SampUtils.getSoftwareVersion().trim().charAt( 0 ) != '?' ); } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/HubTest.java000066400000000000000000000025051356450004300244030ustar00rootroot00000000000000package org.astrogrid.samp; import java.io.IOException; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import junit.framework.TestCase; import org.astrogrid.samp.test.CalcStorm; import org.astrogrid.samp.test.Calculator; import org.astrogrid.samp.test.HubTester; import org.astrogrid.samp.xmlrpc.StandardTestProfile; /** * Hub test case. * Most of the hard work is done by the hub test classes which are part of * the Java SAMP distribution itself, which know how to put a third-party * hub through its paces. * This class mainly just starts up a hub and invokes those tests. * * @author Mark Taylor * @since 29 Jul 2008 */ public class HubTest extends TestCase { protected void setUp() throws IOException { Logger.getLogger( "org.astrogrid.samp" ).setLevel( Level.SEVERE ); } public void testHubTester() throws Exception { Random random = new Random( 23 ); TestProfile[] profiles = TestProfile.createTestProfiles( random ); for ( int i = 0; i < profiles.length; i++ ) { TestProfile profile = profiles[ i ]; profile.startHub(); new HubTester( profile ).run(); new CalcStorm( profile, random, 8, 8, Calculator.RANDOM_MODE ) .run(); profile.stopHub(); } } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/SampUtilsTest.java000066400000000000000000000134771356450004300256200ustar00rootroot00000000000000package org.astrogrid.samp; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Random; import junit.framework.TestCase; public class SampUtilsTest extends TestCase { private final Random random_ = new Random( 232323L ); public void testCodecs() { for ( int k = 0; k < 100; k++ ) { boolean b = random_.nextBoolean(); int i = random_.nextInt(); long l = random_.nextLong(); double f = random_.nextDouble(); assertEquals( b, SampUtils .decodeBoolean( SampUtils.encodeBoolean( b ) ) ); assertEquals( i, SampUtils .decodeInt( SampUtils.encodeInt( i ) ) ); assertEquals( l, SampUtils .decodeLong( SampUtils.encodeLong( l ) ) ); assertEquals( f, SampUtils .decodeFloat( SampUtils.encodeFloat( f ) ), 0.0 ); } } public void testUriCodecs() { assertEquals( "abc", SampUtils.uriEncode( "abc" ) ); assertEquals( "abc", SampUtils.uriDecode( "abc" ) ); assertEquals( "a b+c+d", SampUtils.uriDecode( "a%20b+c%2Bd" ) ); String[] txts = { "abc", "http://localhost/file", "a b c * / % ++--%1", }; for ( int i = 0; i < txts.length; i++ ) { String txt = txts[ i ]; assertEquals( txt, SampUtils.uriDecode( SampUtils.uriEncode( txt ) ) ); assertEquals( txt, SampUtils.uriDecode( SampUtils.uriDecode( SampUtils.uriEncode( SampUtils.uriEncode( txt ) ) ) ) ); } } public void testChars() { assertTrue( SampUtils.isStringChar( 'x' ) ); assertTrue( SampUtils.isStringChar( 'X' ) ); assertTrue( SampUtils.isStringChar( (char) 0x09 ) ); assertTrue( SampUtils.isStringChar( (char) 0x0a ) ); assertTrue( SampUtils.isStringChar( (char) 0x0d ) ); assertTrue( ! SampUtils.isStringChar( (char) 0 ) ); assertTrue( ! SampUtils.isStringChar( (char) 0x0e ) ); assertTrue( ! SampUtils.isStringChar( '\b' ) ); assertTrue( ! SampUtils.isStringChar( (char) 0xffef ) ); } public void testChecks() { goodObject( "a" ); goodObject( "abc\t\r\n" ); badObject( "abc\b" ); badObject( new Integer( 23 ) ); badObject( new Double( Math.E ) ); badObject( null ); badObject( Arrays.asList( new Object[] { "AA", null, } ) ); List abclist = Arrays.asList( new Object[] { "a", "bb", "ccc" } ); goodObject( abclist ); goodObject( new ArrayList() ); goodObject( new HashMap() ); Map xmap = new HashMap(); xmap.put( "xx", abclist ); goodObject( xmap ); xmap.put( abclist, "xx" ); badObject( xmap ); } public void testHostname() throws UnknownHostException { String hprop = SampUtils.LOCALHOST_PROP; Properties sysprops = System.getProperties(); String prop = sysprops.getProperty( hprop ); sysprops.remove( hprop ); assertEquals( "127.0.0.1", SampUtils.getLocalhost() ); sysprops.setProperty( hprop, "host-with-the-most" ); assertEquals( "host-with-the-most", SampUtils.getLocalhost() ); sysprops.setProperty( hprop, "[hostname]" ); assertEquals( InetAddress.getLocalHost().getCanonicalHostName(), SampUtils.getLocalhost() ); if ( prop == null ) { sysprops.remove( hprop ); } else { sysprops.setProperty( hprop, prop ); } } public void testJson() { Map m = new LinkedHashMap(); m.put( "one", "1" ); m.put( "two", "2" ); m.put( "list", Arrays.asList( new String[] { "A", "B", "C" } ) ); String js = "{\"one\":\"1\",\"two\":\"2\",\"list\":[\"A\",\"B\",\"C\"]}"; assertEquals( nows( js ), nows( SampUtils.toJson( m, false ) ) ); assertEquals( nows( js ), nows( SampUtils.toJson( m, true ) ) ); assertEquals( nows( js ), nows( SampUtils.toJson( SampUtils.fromJson( js ), true ) ) ); assertTrue( SampUtils.toJson( m, false ).indexOf( '\n' ) < 0 ); assertTrue( SampUtils.toJson( m, true ).indexOf( '\n' ) >= 0 ); assertEquals( new HashMap(), SampUtils.fromJson("{}") ); assertEquals( new HashMap(), SampUtils.fromJson("{ }") ); assertEquals( new ArrayList(), SampUtils.fromJson("[]") ); assertEquals( new ArrayList(), SampUtils.fromJson("[ ]") ); } private static String nows( String txt ) { return txt.replaceAll( "\\s+", "" ); } private void goodObject( Object obj ) { SampUtils.checkObject( obj ); SampUtils.checkList( Arrays.asList( new Object[] { "a", obj, "c" } ) ); Map map = new HashMap(); map.put( "k1", "v1" ); map.put( "key", obj ); map.put( "k2", "v2" ); SampUtils.checkMap( map ); } private void badObject( Object obj ) { try { SampUtils.checkObject( obj ); fail( "Object should be bad: " + obj ); } catch ( DataException e ) { return; } try { SampUtils.checkList( Arrays.asList( new Object[] { "a", obj, "c" } ) ); fail( "Object should be bad: " + obj ); } catch ( DataException e ) { return; } } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/TestProfile.java000066400000000000000000000067161356450004300252750ustar00rootroot00000000000000package org.astrogrid.samp; import java.io.IOException; import java.util.Random; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.hub.BasicHubService; import org.astrogrid.samp.hub.Hub; import org.astrogrid.samp.hub.HubProfile; import org.astrogrid.samp.hub.HubService; import org.astrogrid.samp.hub.MessageRestriction; import org.astrogrid.samp.hub.ProfileToken; import org.astrogrid.samp.web.WebTestProfile; import org.astrogrid.samp.xmlrpc.StandardTestProfile; import org.astrogrid.samp.xmlrpc.SampXmlRpcClientFactory; import org.astrogrid.samp.xmlrpc.SampXmlRpcServerFactory; import org.astrogrid.samp.xmlrpc.XmlRpcKit; /** * Abstract class providing both a hub and a client profile; * the client profile talks to the hub. * This can be used for isolated testing of hub/client profile pairs. * For use within a test framework implementations should normally take * care not to use the resources (well-known lockfile locations, * well-known ports etc) which are commonly used for normal SAMP operations. * * @author Mark Taylor * @since 4 Feb 2011 */ public abstract class TestProfile implements ClientProfile { private final Random random_; private final ProfileToken directToken_; private Hub hub_; private HubService service_; protected TestProfile( Random random ) { random_ = random; directToken_ = new ProfileToken() { public String getProfileName() { return ""; } public MessageRestriction getMessageRestriction() { return null; } }; } public synchronized void startHub() throws IOException { if ( hub_ != null ) { throw new IllegalStateException( "Hub not stopped " + "due to earlier test failure?" ); } service_ = new BasicHubService( createRandom() ); hub_ = new Hub( service_ ); service_.start(); hub_.startProfile( createHubProfile() ); } public synchronized void stopHub() { if ( hub_ == null ) { throw new IllegalStateException(); } hub_.shutdown(); hub_ = null; service_ = null; } public HubConnection registerDirect() throws SampException { return service_.register( directToken_ ); } public abstract HubProfile createHubProfile() throws IOException; public Random createRandom() { return new Random( random_.nextLong() ); } /** * Returns an array of TestProfiles suitable for performing tests on. */ public static TestProfile[] createTestProfiles( Random random ) throws IOException { SampXmlRpcClientFactory aClient = XmlRpcKit.APACHE.getClientFactory(); SampXmlRpcServerFactory aServ = XmlRpcKit.APACHE.getServerFactory(); SampXmlRpcClientFactory iClient = XmlRpcKit.INTERNAL.getClientFactory(); SampXmlRpcServerFactory iServ = XmlRpcKit.INTERNAL.getServerFactory(); return new TestProfile[] { new StandardTestProfile( random, aClient, aServ, iClient, iServ ), new StandardTestProfile( random, iClient, iServ, aClient, aServ ), new StandardTestProfile( random, iClient, iServ, iClient, iServ ), new WebTestProfile( random, true, null ), }; } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/bridge/000077500000000000000000000000001356450004300234145ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/bridge/BridgeTest.java000066400000000000000000000255451356450004300263260ustar00rootroot00000000000000package org.astrogrid.samp.bridge; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.URL; import java.util.HashSet; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import junit.framework.TestCase; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.TestProfile; import org.astrogrid.samp.bridge.Bridge; import org.astrogrid.samp.client.AbstractMessageHandler; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.HubConnector; import org.astrogrid.samp.client.ResultHandler; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.UtilServer; import org.astrogrid.samp.xmlrpc.StandardTestProfile; import org.astrogrid.samp.xmlrpc.XmlRpcKit; public class BridgeTest extends TestCase { private final static String ECHO_MTYPE = "test.echo"; public void setUp() { Logger.getLogger( "org.astrogrid.samp" ).setLevel( Level.WARNING ); Logger.getLogger( "org.astrogrid.samp.xmlrpc.internal" ) .setLevel( Level.SEVERE ); Logger.getLogger( "org.astrogrid.samp.xmlrpc.StandardHubProfile" ) .setLevel( Level.SEVERE ); } public void testBridge() throws IOException, SampException, InterruptedException { bridgeTest( 2 ); bridgeTest( 4 ); } public void testExport() throws IOException { File file = File.createTempFile( "test", ".txt" ); file.deleteOnExit(); OutputStream out = new FileOutputStream( file ); byte[] text = new byte[] { (byte) 'g', (byte) 'u', (byte) 'r', }; out.write( text ); out.close(); String hostname = InetAddress.getLocalHost().getCanonicalHostName(); UrlExporter exporter1 = new UrlExporter( hostname, true ); UrlExporter exporter2 = new UrlExporter( hostname, false ); assertEquals( "http://" + hostname + "/gur", exporter1.exportString( "http://127.0.0.1/gur" ) ); String rawUrl = file.toURL().toString() // fix broken file.toURL() .replaceFirst( "^file:/\\b", "file://localhost/" ); URL expUrl1 = new URL( exporter1.exportString( rawUrl ) ); URL expUrl2 = new URL( exporter2.exportString( rawUrl ) ); assertEquals( "http", expUrl1.getProtocol() ); assertEquals( "file", expUrl2.getProtocol() ); assertEquals( rawUrl, expUrl2.toString() ); InputStream expIn1 = expUrl1.openStream(); InputStream expIn2 = expUrl2.openStream(); for ( int i = 0; i < text.length; i++ ) { assertEquals( expIn1.read(), text[ i ] ); assertEquals( expIn2.read(), text[ i ] ); } expIn1.close(); expIn2.close(); file.delete(); } private void bridgeTest( int nhub ) throws IOException, SampException, InterruptedException { Random random = new Random( 232323 ); XmlRpcKit kit = XmlRpcKit.getInstance(); // Set up N hubs with one client each. // Clients all have similar (not identical) metadata, and all // subscribe to MTYPE_ECHO. TestProfile[] profiles = new TestProfile[ nhub ]; HubConnector[] connectors = new HubConnector[ nhub ]; HubConnection[] connections = new HubConnection[ nhub ]; for ( int ih = 0; ih < nhub; ih++ ) { final int ih1 = ih + 1; final char iha = (char) ( ih + 'A' ); TestProfile profile = new StandardTestProfile( random, kit ) { public String toString() { return "P." + iha; } }; profiles[ ih ] = profile; profile.startHub(); HubConnector connector = new HubConnector( profile ); connectors[ ih ] = connector; Metadata meta = new Metadata(); meta.setName( "Client " + ih1 ); meta.put( "client.id", "C" + ih1 ); connector.declareMetadata( meta ); connector.addMessageHandler( new EchoMessageHandler() ); connector.declareSubscriptions( connector.computeSubscriptions() ); connections[ ih ] = connector.getConnection(); assertNotNull( connections[ ih ] ); } // Wait for all metadata and subscriptions. for ( int ih = 0; ih < nhub; ih++ ) { Map clientMap = connectors[ ih ].getClientMap(); synchronized ( clientMap ) { while ( ! hasAtts( clientMap, true, true ) ) { clientMap.wait(); } } } // Check no (other) subscribed clients, one other client (the hub). for ( int ih = 0; ih < nhub; ih++ ) { HubConnection connection = connections[ ih ]; assertEquals( 0, connection.getSubscribedClients( ECHO_MTYPE ) .size() ); assertEquals( 1, connection.getRegisteredClients().length ); } // Start bridge. Bridge bridge = new Bridge( profiles ); bridge.start(); // Wait for all metadata and subscriptions from bridge start. for ( int ih = 0; ih < nhub; ih++ ) { Map clientMap = connectors[ ih ].getClientMap(); synchronized ( clientMap ) { while ( ! hasAtts( clientMap, true, true ) ) { clientMap.wait(); } } } // Check that all proxy and bridge clients we expect to have appeared // are present and correct. for ( int ih = 0; ih < nhub; ih++ ) { HubConnection connection = connections[ ih ]; Map clientMap = connectors[ ih ].getClientMap(); assertEquals( nhub - 1, connection.getSubscribedClients( ECHO_MTYPE ) .size() ); assertEquals( ( nhub - 1 ) + 2, connection.getRegisteredClients().length ); assertEquals( ( nhub - 1 ), getProxyCount( clientMap ) ); assertTrue( hasBridge( clientMap ) ); Set srcSet = new HashSet(); for ( Iterator it = clientMap.values().iterator(); it.hasNext(); ) { Client client = (Client) it.next(); srcSet.add( client.getMetadata().get( "bridge.proxy.source" ) ); } assertTrue( srcSet.contains( null ) ); assertEquals( nhub, srcSet.size() ); } // Send one echo broadcast from each client. This should get // anwered over the bridge from all the remote clients. EchoResultHandler[] resultHandlers = new EchoResultHandler[ nhub ]; for ( int ih = 0; ih < nhub; ih++ ) { Map echoContent = new HashMap(); echoContent.put( "text", "gur" ); echoContent.put( "index", "ix" + ( ih + 1 ) ); Message msg = new Message( ECHO_MTYPE, echoContent ); resultHandlers[ ih ] = new EchoResultHandler( echoContent ); connectors[ ih ].callAll( msg, resultHandlers[ ih ], 5 ); } // Check the right number of results are in. for ( int ih = 0; ih < nhub; ih++ ) { EchoResultHandler handler = resultHandlers[ ih ]; synchronized ( handler ) { while ( ! handler.isDone_ ) { handler.wait(); } } assertEquals( nhub - 1, handler.okResults_ ); } // Stop the bridge. bridge.stop(); // Check all clients have gone away. for ( int ih = 0; ih < nhub; ih++ ) { Map clientMap = connectors[ ih ].getClientMap(); synchronized ( clientMap ) { while ( getProxyCount( clientMap ) > 0 || hasBridge( clientMap ) ) { clientMap.wait(); } } assertEquals( 2, clientMap.size() ); } } private boolean hasAtts( Map clientMap, boolean wantMeta, boolean wantSubs ) { for ( Iterator it = clientMap.values().iterator(); it.hasNext(); ) { Client client = (Client) it.next(); if ( wantMeta ) { Metadata meta = client.getMetadata(); if ( meta == null || meta.isEmpty() ) { return false; } } if ( wantSubs ) { Subscriptions subs = client.getSubscriptions(); if ( subs == null || subs.isEmpty() ) { return false; } } } return true; } private int getProxyCount( Map clientMap ) { int nProxy = 0; for ( Iterator it = clientMap.values().iterator(); it.hasNext(); ) { Client client = (Client) it.next(); if ( client.getMetadata().containsKey( "bridge.proxy.source" ) ) { nProxy++; } } return nProxy; } private boolean hasBridge( Map clientMap ) { for ( Iterator it = clientMap.values().iterator(); it.hasNext(); ) { Client client = (Client) it.next(); if ( client.getMetadata().getName().equalsIgnoreCase( "bridge" ) ) { return true; } } return false; } private static class EchoMessageHandler extends AbstractMessageHandler { EchoMessageHandler() { super( ECHO_MTYPE ); } public Map processCall( HubConnection conn, String senderId, Message msg ) { return msg.getParams(); } } private static class EchoResultHandler implements ResultHandler { private final Map expectedResult_; int okResults_; boolean isDone_; EchoResultHandler( Map expectedResult ) { expectedResult_ = new HashMap( expectedResult ); } public synchronized void result( Client responder, Response response ) { if ( response.isOK() ) { Map result = response.getResult(); if ( expectedResult_.equals( response.getResult() ) ) { okResults_++; } else { System.err.println( "echo mismatch" ); } } else { System.err.println( "echo error response" ); } } public synchronized void done() { isDone_ = true; notifyAll(); } } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/client/000077500000000000000000000000001356450004300234365ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/client/HubConnectorTest.java000066400000000000000000000302671356450004300275420ustar00rootroot00000000000000package org.astrogrid.samp.client; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import junit.framework.TestCase; import org.astrogrid.samp.Client; import org.astrogrid.samp.Message; import org.astrogrid.samp.Metadata; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.TestProfile; import org.astrogrid.samp.xmlrpc.internal.InternalServer; public class HubConnectorTest extends TestCase { private final Random random_ = new Random( 2323L ); private static final String ECHO_MTYPE = "test.echo"; protected void setUp() { Logger.getLogger( "org.astrogrid.samp" ).setLevel( Level.SEVERE ); } public void testConnector() throws IOException, InterruptedException { TestProfile[] profiles = TestProfile.createTestProfiles( random_ ); for ( int i = 0; i < profiles.length; i++ ) { testConnector( profiles[ i ] ); } } private void testConnector( TestProfile profile ) throws IOException, InterruptedException { assertNull( profile.register() ); HubConnector connector = new HubConnector( profile ); connector.setAutoconnect( 1 ); connector.declareSubscriptions( connector.computeSubscriptions() ); Map clientMap = connector.getClientMap(); assertTrue( clientMap.isEmpty() ); Metadata meta = new Metadata(); meta.setName( "HubConnectorTest" ); meta.put( "colour", "yellow" ); connector.declareMetadata( meta ); assertEquals( meta, connector.getMetadata() ); assertTrue( ! connector.isConnected() ); profile.startHub(); HubConnection c0 = profile.register(); assertNotNull( c0 ); assertEquals( new HashMap(), getSubscriptions( c0 ) ); // Sometimes these tests just don't work. It's because of the // indeterminate nature of SAMP - message delivery order is not // guaranteed etc. I could remove these tests but .. most of the // time they should, and do, pass. synchronized ( clientMap ) { while ( clientMap.size() != 3 ) clientMap.wait(); } c0.unregister(); synchronized ( clientMap ) { while ( clientMap.size() != 2 ) clientMap.wait(); } assertTrue( connector.isConnected() ); assertNotNull( connector.getConnection() ); assertEquals( 2, clientMap.size() ); assertTrue( clientMap.containsKey( connector.getConnection() .getRegInfo().getHubId() ) ); assertTrue( clientMap.containsKey( connector.getConnection() .getRegInfo().getSelfId() ) ); assertEquals( meta, connector.getMetadata() ); assertEquals( meta, getMetadata( connector.getConnection() ) ); String selfId = connector.getConnection().getRegInfo().getSelfId(); synchronized ( clientMap ) { while ( ! meta.equals( ((Client) clientMap.get( selfId )) .getMetadata() ) ) { clientMap.wait(); } } Subscriptions subs = connector.getSubscriptions(); assertTrue( subs.containsKey( "samp.hub.event.register" ) ); assertTrue( ! subs.containsKey( ECHO_MTYPE ) ); assertEquals( subs, getSubscriptions( connector.getConnection() ) ); synchronized ( clientMap ) { while ( ! subs.equals( ((Client) clientMap.get( selfId )) .getSubscriptions() ) ) { clientMap.wait(); } } connector.addMessageHandler( new TestMessageHandler() ); subs = connector.computeSubscriptions(); assertTrue( subs.containsKey( ECHO_MTYPE ) ); assertTrue( ! subs.equals( connector.getSubscriptions() ) ); assertTrue( ! subs .equals( getSubscriptions( connector.getConnection() ) ) ); connector.declareSubscriptions( subs ); assertEquals( subs, connector.getSubscriptions() ); assertEquals( subs, getSubscriptions( connector.getConnection() ) ); assertEquals( meta, ((Client) clientMap.get( connector.getConnection() .getRegInfo().getSelfId() )) .getMetadata() ); meta.put( "colour", "blue" ); connector.declareMetadata( meta ); assertEquals( meta, connector.getMetadata() ); assertEquals( meta, getMetadata( connector.getConnection() ) ); delay( 500 ); assertEquals( subs, ((Client) clientMap.get( connector.getConnection() .getRegInfo().getSelfId() )) .getSubscriptions() ); assertEquals( meta, ((Client) clientMap.get( connector.getConnection() .getRegInfo().getSelfId() )) .getMetadata() ); RegInfo regInfo0 = connector.getConnection().getRegInfo(); connector.setActive( false ); assertNull( connector.getConnection() ); assertTrue( clientMap.isEmpty() ); connector.setActive( true ); RegInfo regInfo1 = connector.getConnection().getRegInfo(); assertTrue( ! regInfo0.getSelfId().equals( regInfo1.getSelfId() ) ); assertEquals( regInfo0.getHubId(), regInfo1.getHubId() ); connector.setAutoconnect( 0 ); profile.stopHub(); delay( 500 ); assertNull( connector.getConnection() ); assertTrue( clientMap.isEmpty() ); profile.startHub(); RegInfo regInfo2 = connector.getConnection().getRegInfo(); assertTrue( ! regInfo0.getPrivateKey() .equals( regInfo2.getPrivateKey() ) ); assertTrue( ! regInfo1.getPrivateKey() .equals( regInfo2.getPrivateKey() ) ); assertEquals( 2, clientMap.size() ); assertEquals( meta, getMetadata( connector.getConnection() ) ); assertEquals( subs, getSubscriptions( connector.getConnection() ) ); assertTrue( clientMap.containsKey( connector.getConnection() .getRegInfo().getHubId() ) ); assertTrue( clientMap.containsKey( connector.getConnection() .getRegInfo().getSelfId() ) ); connector.getConnection().unregister(); profile.stopHub(); } public void testSynch() throws IOException { TestProfile[] profiles = TestProfile.createTestProfiles( random_ ); for ( int i = 0; i < profiles.length; i++ ) { testSynch( profiles[ i ] ); } } private void testSynch( TestProfile profile ) throws IOException { profile.startHub(); TestMessageHandler echo = new TestMessageHandler(); HubConnector c1 = new HubConnector( profile ); String id1 = c1.getConnection().getRegInfo().getSelfId(); c1.addMessageHandler( echo ); Subscriptions subs1 = new Subscriptions(); subs1.addMType( ECHO_MTYPE ); c1.declareSubscriptions( subs1 ); HubConnector c2 = new HubConnector( profile ); String id2 = c2.getConnection().getRegInfo().getSelfId(); c2.addMessageHandler( echo ); c2.declareSubscriptions( c2.computeSubscriptions() ); Map params = new HashMap(); params.put( "names", Arrays.asList( new String[] { "Arthur", "Gordon", "Pym" } ) ); Message msg = new Message( ECHO_MTYPE, params ); msg.check(); Response r1 = c1.callAndWait( id2, msg, 0 ); Response r2 = c2.callAndWait( id1, msg, 0 ); assertEquals( Response.OK_STATUS, r1.getStatus() ); assertEquals( Response.OK_STATUS, r2.getStatus() ); assertEquals( params, r1.getResult() ); assertEquals( params, r2.getResult() ); TestResultHandler tHandler = new TestResultHandler(); c1.call( id2, msg, tHandler, 0 ); int millis = tHandler.waitTillDone(); assertTrue( tHandler.getResponse( id2 ).isOK() ); Message msg5 = new Message( msg ); msg5.addParam( "waitMillis", "5000" ); TestResultHandler th2 = new TestResultHandler(); TestResultHandler th3 = new TestResultHandler(); c1.call( id2, msg5, th2, 1 ); c1.callAll( msg5, th3, 1 ); assertTrue( ! th2.isDone_ ); assertTrue( ! th3.isDone_ ); assertNull( th2.getResponse( id2 ) ); assertNull( th3.getResponse( id2 ) ); int delay = th2.waitTillDone() + th3.waitTillDone(); assertTrue( delay > 400 ); // should be about 1 sec assertTrue( th2.isDone_ ); assertTrue( th3.isDone_ ); assertNull( th2.getResponse( id2 ) ); assertNull( th3.getResponse( id2 ) ); Message msg02 = new Message( msg ); msg02.addParam( "waitMillis", "200" ); TestResultHandler th4 = new TestResultHandler(); TestResultHandler th5 = new TestResultHandler(); long start02 = System.currentTimeMillis(); c1.call( id2, msg02, th4, 5 ); c1.callAll( msg02, th5, 5 ); assertTrue( ! th4.isDone_ ); assertTrue( ! th5.isDone_ ); th4.waitTillDone(); th5.waitTillDone(); assertTrue( System.currentTimeMillis() - start02 > 150 ); assertTrue( th4.isDone_ ); assertTrue( th5.isDone_ ); assertTrue( th4.getResponse( id2 ).isOK() ); assertTrue( th5.getResponse( id2 ).isOK() ); profile.stopHub(); } private Subscriptions getSubscriptions( HubConnection connection ) throws SampException { return connection .getSubscriptions( connection.getRegInfo().getSelfId() ); } private Metadata getMetadata( HubConnection connection ) throws SampException { return connection.getMetadata( connection.getRegInfo().getSelfId() ); } static void delay( int millis ) { Object o = new Object(); synchronized ( o ) { try { o.wait( millis ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); } } } private static class TestMessageHandler extends AbstractMessageHandler { TestMessageHandler() { super( ECHO_MTYPE ); } public Map processCall( HubConnection conn, String senderId, Message msg ) { String waitParam = (String) msg.getParam( "waitMillis" ); if ( waitParam != null ) { int delay = SampUtils.decodeInt( waitParam ); delay( delay ); } return msg.getParams(); } } private static class TestResultHandler implements ResultHandler { volatile boolean isDone_; final Map resultMap_ = Collections.synchronizedMap( new HashMap() ); public synchronized void result( Client client, Response response ) { resultMap_.put( client.getId(), response ); notifyAll(); } public synchronized void done() { isDone_ = true; notifyAll(); } public Response getResponse( String clientId ) { return (Response) resultMap_.get( clientId ); } public synchronized int waitTillDone() { long start = System.currentTimeMillis(); while ( ! isDone_ ) { try { wait(); } catch ( InterruptedException e ) { fail(); } } return (int) ( System.currentTimeMillis() - start ); } public void reset() { isDone_ = false; resultMap_.clear(); } } public static void main( String[] args ) throws Exception { HubConnectorTest t = new HubConnectorTest(); t.setUp(); t.testConnector(); } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/httpd/000077500000000000000000000000001356450004300233035ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/httpd/ServerTest.java000066400000000000000000000064671356450004300262710ustar00rootroot00000000000000package org.astrogrid.samp.httpd; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import junit.framework.TestCase; public class ServerTest extends TestCase { public void testMultiMapperHandler() throws IOException { HttpServer server = new HttpServer(); MultiURLMapperHandler mHandler = new MultiURLMapperHandler( server, "gur" ); server.addHandler( mHandler ); File f1 = new File( "a/b.fits" ); URL fileUrl1 = f1.toURL(); URL url1 = mHandler.addLocalUrl( f1.toURL() ); assertEquals( "http", url1.getProtocol() ); assertTrue( url1.toString().endsWith( "b.fits" ) ); URL url2 = mHandler .addLocalUrl( ServerTest.class.getResource( "ServerTest.class" ) ); assertEquals( "http", url2.getProtocol() ); assertTrue( url2.toString().endsWith( ".class" ) ); HttpServer.Request req2 = new HttpServer.Request( "GET", url2.getPath(), new HashMap(), new SocketAddress() {}, null ); HttpServer.Response resp2 = server.serve( req2 ); assertEquals( 200, resp2.getStatusCode() ); ByteArrayOutputStream bos = new ByteArrayOutputStream(); resp2.writeBody( bos ); byte[] bosbuf = bos.toByteArray(); assertEquals( (byte) 0xca, bosbuf[ 0 ] ); assertEquals( (byte) 0xfe, bosbuf[ 1 ] ); assertEquals( (byte) 0xba, bosbuf[ 2 ] ); assertEquals( (byte) 0xbe, bosbuf[ 3 ] ); mHandler.removeServerUrl( url2 ); resp2 = server.serve( req2 ); assertEquals( 404, resp2.getStatusCode() ); assertNull( fileUrl1.getRef() ); String frag = "frag"; URL fragUrl1 = new URL( fileUrl1.toString() + "#" + frag ); assertEquals( frag, fragUrl1.getRef() ); assertEquals( frag, mHandler.addLocalUrl( fragUrl1 ).getRef() ); } public void testHeaderMap() { HttpServer.HttpHeaderMap hdrMap = new HttpServer.HttpHeaderMap(); hdrMap.addHeader( "a", "AA" ); hdrMap.addHeader( "b", "BB" ); hdrMap.addHeader( "c", "CC" ); assertEquals( 3, hdrMap.size() ); assertEquals( Arrays.asList( new String[] { "a", "b", "c" } ), new ArrayList( hdrMap.keySet() ) ); assertEquals( "CC", hdrMap.get( "c" ) ); hdrMap.addHeader( "c", "DD" ); assertEquals( Arrays.asList( new String[] { "a", "b", "c" } ), new ArrayList( hdrMap.keySet() ) ); assertEquals( "CC, DD", hdrMap.get( "c" ) ); hdrMap.addHeader( "C", "EE" ); assertEquals( Arrays.asList( new String[] { "a", "b", "c" } ), new ArrayList( hdrMap.keySet() ) ); assertEquals( "CC, DD, EE", hdrMap.get( "c" ) ); assertEquals( "AA", HttpServer.getHeader( hdrMap, "a" ) ); assertEquals( "AA", HttpServer.getHeader( hdrMap, "A" ) ); assertNull( HttpServer.getHeader( hdrMap, "x" ) ); assertNull( HttpServer.getHeader( hdrMap, null ) ); assertEquals( "CC, DD, EE", HttpServer.getHeader( hdrMap, "c" ) ); assertEquals( "CC, DD, EE", HttpServer.getHeader( hdrMap, "C" ) ); } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/web/000077500000000000000000000000001356450004300227355ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/web/I18nTest.java000066400000000000000000000063731356450004300251700ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.lang.reflect.Method; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import junit.framework.TestCase; public class I18nTest extends TestCase { private static final Locale[] locales_ = new Locale[] { Locale.ENGLISH, Locale.ITALIAN, Locale.GERMAN, Locale.FRENCH, }; private static final String[] suffixes_ = new String[] { "_en", "_it", "_de", "_fr", }; private static final Method[] contentMethods_ = AuthResourceBundle.getContentMethods(); public void testTools() throws Exception { assertEquals( toMap( getContent( Locale.ENGLISH ) ), toMap( getContent( Locale.UK ) ) ); } public void testAuthResourceBundle() throws Exception { Set mapSet = new HashSet(); for ( int il = 0; il < locales_.length; il++ ) { Locale locale = locales_[ il ]; ResourceBundle bundle = ResourceBundle.getBundle( AuthResourceBundle.class.getName(), locale ); AuthResourceBundle.checkHasAllKeys( bundle ); AuthResourceBundle.Content content = AuthResourceBundle.getAuthContent( bundle ); Map map = toMap( content ); for ( Iterator it = map.entrySet().iterator(); it.hasNext(); ) { Map.Entry entry = (Map.Entry) it.next(); String value = (String) entry.getValue(); assertNotNull( value ); assertTrue( value.length() > 0 ); } assertTrue( "Reuse content for " + locale, ! mapSet.contains( map ) ); mapSet.add( map ); } } /** * This one does something different - by picking the resources by * locale-specific name, it avoids getting a parent chain. * In this way it can check if any resources are missing * (if you do it the more obvious way, missing ones are filled in by * the parent, which we don't want for testing purposes). */ public void testAuthSuffixes() { for ( int is = 0; is < suffixes_.length; is++ ) { ResourceBundle bundle = ResourceBundle.getBundle( AuthResourceBundle.class.getName() + suffixes_[ is ], Locale.US ); AuthResourceBundle.checkHasAllKeys( bundle ); } } private static AuthResourceBundle.Content getContent( Locale locale ) { ResourceBundle bundle = ResourceBundle.getBundle( AuthResourceBundle.class.getName(), locale ); return AuthResourceBundle.getAuthContent( bundle ); } private static Map toMap( AuthResourceBundle.Content content ) throws Exception { Map map = new HashMap(); for ( int im = 0; im < contentMethods_.length; im++ ) { Method method = contentMethods_[ im ]; map.put( method.getName(), (String) method.invoke( content, new Object[ 0 ] ) ); } return map; } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/web/WebClientTest.java000066400000000000000000000356211356450004300263230ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import junit.framework.TestCase; import org.astrogrid.samp.Message; import org.astrogrid.samp.RegInfo; import org.astrogrid.samp.Response; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.Subscriptions; import org.astrogrid.samp.TestProfile; import org.astrogrid.samp.client.CallableClient; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.HttpServer; import org.astrogrid.samp.hub.MessageRestriction; import org.astrogrid.samp.xmlrpc.XmlRpcHubConnection; import org.astrogrid.samp.xmlrpc.internal.InternalServer; public class WebClientTest extends TestCase { public void setUp() { Logger.getLogger( "org.astrogrid.samp" ).setLevel( Level.WARNING ); Logger.getLogger( InternalServer.class.getName() ) .setLevel( Level.SEVERE ); Logger.getLogger( WebHubConnection.class.getName() ) .setLevel( Level.SEVERE ); Logger.getLogger( UrlTracker.class.getName() ) .setLevel( Level.SEVERE ); Logger.getLogger( HttpServer.class.getName() ) .setLevel( Level.SEVERE ); } // Test withdrawn, multiple cycles of allowReverseCallbacks are not // recommended. Code may come in useful for something else another // day though. public void doNottestCallbacks() throws IOException, InterruptedException { WebTestProfile profile = new WebTestProfile( new Random( 23L ), true, null ); profile.startHub(); XmlRpcHubConnection tconn = (XmlRpcHubConnection) profile.register(); XmlRpcHubConnection rconn = (XmlRpcHubConnection) profile.register(); String tid = tconn.getRegInfo().getSelfId(); String rid = rconn.getRegInfo().getSelfId(); Subscriptions subs = new Subscriptions(); String pMtype = "test.ping"; subs.addMType( pMtype ); Message pmsg = new Message( pMtype ); Receiver rcvr = new Receiver(); rconn.setCallable( rcvr ); rconn.declareSubscriptions( subs ); tconn.notify( rid, pmsg ); assertTrue( rcvr.waitForMessage( 1000 ) != null ); rconn.exec( "allowReverseCallbacks", new String[] { "0" } ); tconn.notify( rid, pmsg ); assertTrue( rcvr.waitForMessage( 1000 ) == null ); rconn.exec( "allowReverseCallbacks", new String[] { "1" } ); assertTrue( rcvr.waitForMessage( 1000 ) != null ); assertTrue( rcvr.waitForMessage( 1000 ) == null ); tconn.unregister(); rconn.unregister(); } public void testRegistration() throws IOException { WebTestProfile profile = new WebTestProfile( new Random( 23L ), true, null ); profile.startHub(); assertTrue( profile.isHubRunning() ); HubConnection conn = profile.register(); assertEquals( "samp.url-translator", WebClientProfile.URLTRANS_KEY ); RegInfo regInfo = conn.getRegInfo(); assertTrue( ((String) regInfo.get( "samp.private-key" )).length() > 0 ); profile.setClientAuthorizer( ClientAuthorizers .createFixedClientAuthorizer( false ) ); try { profile.register(); fail(); } catch ( SampException e ) { assertTrue( e.getMessage().indexOf( "denied" ) > 0 ); } profile.stopHub(); } public void testControlledUrlTranslator() throws IOException, InterruptedException { WebTestProfile profile = new WebTestProfile( new Random( 23L ), true, null ); profile.startHub(); Subscriptions subs = new Subscriptions(); String mtype = "test.url"; subs.addMType( mtype ); String ftxt = "some\ntext\nin\na\nfile\n"; HubConnection webConn = profile.register(); Receiver webReceiver = new Receiver(); webConn.setCallable( webReceiver ); webConn.declareSubscriptions( subs ); RegInfo webReg = webConn.getRegInfo(); String turl = (String) webReg.get( WebClientProfile.URLTRANS_KEY ); HubConnection dirConn = profile.registerDirect(); Receiver dirReceiver = new Receiver(); dirConn.setCallable( dirReceiver ); dirConn.declareSubscriptions( subs ); RegInfo dirReg = dirConn.getRegInfo(); File file1 = File.createTempFile( "txtfile", ".tmp" ); file1.deleteOnExit(); OutputStream fout1 = new FileOutputStream( file1 ); fout1.write( ftxt.getBytes( "utf-8" ) ); fout1.close(); URL furl1 = SampUtils.fileToUrl( file1 ); URL tfurl1 = new URL( turl + furl1.toString() ); Message url1Msg = new Message( mtype ) .addParam( "url", furl1.toString() ); // First try - blocked because it's a local URL, and no incoming // reference has been seen (UrlTracker). try { readUrl( tfurl1 ); fail( "Should have blocked access to " + tfurl1 ); } catch ( IOException e ) { } // Second try: send message out mentioning it. This will still // fail, because we're not trusted, and it will permanently block // access (if we're the first one to mention it, it's probalbly // not legitimante and it might provoke an echo somewhere). webConn.notify( dirReg.getSelfId(), url1Msg ); dirReceiver.waitForMessage( 1000 ); try { readUrl( tfurl1 ); fail( "Should have blocked access to " + tfurl1 ); } catch ( IOException e ) { } // Third try: get some trusted client to mention the URL, // but since it's been permanently blocked, we still can't access it. dirConn.notify( webReg.getSelfId(), url1Msg ); webReceiver.waitForMessage( 1000 ); try { readUrl( tfurl1 ); fail( "Should have blocked access to " + tfurl1 ); } catch ( IOException e ) { } // Now try with a different file. File file2 = File.createTempFile( "txtfile", ".tmp" ); file2.deleteOnExit(); OutputStream fout2 = new FileOutputStream( file2 ); fout2.write( ftxt.getBytes( "utf-8" ) ); fout2.close(); URL furl2 = SampUtils.fileToUrl( file2 ); URL tfurl2 = new URL( turl + furl2.toString() ); Message url2msg = new Message( mtype ) .addParam( "url", furl2.toString() ); // Get a trusted client to mention it. dirConn.notify( webReg.getSelfId(), url2msg ); webReceiver.waitForMessage( 1000 ); // Read it. Should be OK. assertEquals( ftxt, readUrl( tfurl2 ) ); // Tidy up. file1.delete(); file2.delete(); profile.stopHub(); } public void testUncontrolledUrlTranslator() throws IOException { WebTestProfile profile = new WebTestProfile( new Random( 29L ), false, null ); profile.startHub(); HubConnection webConn = profile.register(); RegInfo webReg = webConn.getRegInfo(); String turl = (String) webReg.get( WebClientProfile.URLTRANS_KEY ); File file = File.createTempFile( "txtfile", ".tmp" ); file.deleteOnExit(); OutputStream fout = new FileOutputStream( file ); String ftxt = "some\ntext\nin\na\nfile\n"; fout.write( ftxt.getBytes( "utf-8" ) ); fout.close(); URL furl = SampUtils.fileToUrl( file ); URL tfurl = new URL( turl + furl.toString() ); assertEquals( ftxt, readUrl( tfurl ) ); file.delete(); profile.stopHub(); } public void testRestrictMTypes() throws IOException, InterruptedException { assertTrue( passMessage( ListMessageRestriction.ALLOW_ALL, "test.hello" ) ); assertTrue( ! passMessage( ListMessageRestriction.DENY_ALL, "test.hello" ) ); assertTrue( passMessage( singleMessageRestriction( true, "test.hello" ), "test.hello" ) ); assertTrue( ! passMessage( singleMessageRestriction( true, "test.goodbye" ), "test.hello" ) ); assertTrue( passMessage( singleMessageRestriction( false, "test.goodbye" ), "test.hello" ) ); assertTrue( ! passMessage( singleMessageRestriction( false, "test.goodbye" ), "test.goodbye" ) ); assertTrue( passMessage( singleMessageRestriction( true, "test.*" ), "test.hello" ) ); assertTrue( ! passMessage( singleMessageRestriction( true, "test.*" ), "hello" ) ); } private MessageRestriction singleMessageRestriction( final boolean allow, String mtype ) { return new ListMessageRestriction( allow, new String[] { mtype }, true ); } private boolean passMessage( MessageRestriction mrestrict, String mtype ) throws IOException, InterruptedException { Message msg0 = new Message( mtype ); WebTestProfile profile = new WebTestProfile( new Random( 1019L ), false, mrestrict ); profile.startHub(); HubConnection tconn = profile.register(); HubConnection rconn = profile.register(); String tid = tconn.getRegInfo().getSelfId(); String rid = rconn.getRegInfo().getSelfId(); Subscriptions subs = new Subscriptions(); subs.addMType( mtype ); Receiver rcvr = new Receiver(); rconn.setCallable( rcvr ); rconn.declareSubscriptions( subs ); boolean sent; try { tconn.notify( rid, msg0 ); sent = true; } catch ( SampException e ) { sent = false; } if ( sent ) { Message msg1 = rcvr.waitForMessage( 1000 ); assertEquals( msg0, msg1 ); } tconn.unregister(); rconn.unregister(); return sent; } public void testServer() throws IOException { ServerSocket sock = new ServerSocket( 0 ); String path = "/"; WebHubProfile.ServerFactory sxfact = new WebHubProfile.ServerFactory(); sxfact.setLogType( null ); sxfact.setPort( 0 ); sxfact.setXmlrpcPath( "/" ); sxfact.setOriginAuthorizer( OriginAuthorizers.TRUE ); sxfact.setAllowFlash( true ); sxfact.setAllowSilverlight( true ); InternalServer xServer = sxfact.createSampXmlRpcServer(); HttpServer hServer = xServer.getHttpServer(); URL surl = new URL( "http://localhost:" + hServer.getSocket().getLocalPort() ); hServer.start(); String xdomain = readUrl( new URL( surl, "/crossdomain.xml" ) ); assertTrue( xdomain.indexOf( "" ) > 0 ); String accpol = readUrl( new URL( surl, "/clientaccesspolicy.xml" ) ); assertTrue( accpol.indexOf( "" ) > 0 ); hServer.stop(); } public void testMessageRestriction() { MessageRestriction allMr = ListMessageRestriction.ALLOW_ALL; MessageRestriction noneMr = ListMessageRestriction.DENY_ALL; MessageRestriction dfltMr = ListMessageRestriction.DEFAULT; MessageRestriction testMr = new ListMessageRestriction( true, new String[] { "test.*" }, true ); MessageRestriction notestMr = new ListMessageRestriction( false, new String[] { "test.*" }, true ); assertTrue( allMr.permitSend( "system.exec", new HashMap() ) ); assertTrue( ! noneMr.permitSend( "system.exec", new HashMap() ) ); assertTrue( ! dfltMr.permitSend( "system.exec", new HashMap() ) ); assertTrue( ! testMr.permitSend( "system.exec", new HashMap() ) ); assertTrue( notestMr.permitSend( "system.exec", new HashMap() ) ); assertTrue( dfltMr.permitSend( "table.load.votable", new HashMap() ) ); assertTrue( new ListMessageRestriction( true, new String[] { "do.what" }, true ) .permitSend( "do.what", new HashMap() ) ); } private static String readUrl( URL url ) throws IOException { StringBuffer ubuf = new StringBuffer(); InputStream in = url.openStream(); for ( int b; ( b = in.read() ) >= 0; ) { ubuf.append( (char) b ); } in.close(); return ubuf.toString(); } private static class Receiver implements CallableClient { private final List msgList_ = new ArrayList(); private final List responseList_ = new ArrayList(); public synchronized void receiveCall( String senderId, String msgId, Message msg ) { msgList_.add( msg ); notifyAll(); } public synchronized void receiveNotification( String senderId, Message msg ) { msgList_.add( msg ); notifyAll(); } public synchronized void receiveResponse( String responsderId, String msgTag, Response response ) { msgList_.add( response ); notifyAll(); } public synchronized Message waitForMessage( long millis ) throws InterruptedException { long end = System.currentTimeMillis() + millis; while ( System.currentTimeMillis() < end && msgList_.isEmpty() ) { wait( end - System.currentTimeMillis() ); } return msgList_.isEmpty() ? null : (Message) msgList_.get( 0 ); } public synchronized Response waitForResponse( long millis ) throws InterruptedException { long end = System.currentTimeMillis() + millis; while ( System.currentTimeMillis() < end && responseList_.isEmpty() ) { wait( end - System.currentTimeMillis() ); } return responseList_.isEmpty() ? null : (Response) responseList_.get( 0 ); } } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/web/WebTestProfile.java000066400000000000000000000110451356450004300264770ustar00rootroot00000000000000package org.astrogrid.samp.web; import java.io.IOException; import java.net.ConnectException; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; import java.net.MalformedURLException; import java.util.HashMap; import java.util.Map; import java.util.Random; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.TestProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.httpd.HttpServer; import org.astrogrid.samp.hub.HubProfile; import org.astrogrid.samp.hub.KeyGenerator; import org.astrogrid.samp.hub.MessageRestriction; import org.astrogrid.samp.xmlrpc.SampXmlRpcClientFactory; import org.astrogrid.samp.xmlrpc.XmlRpcKit; import org.astrogrid.samp.xmlrpc.internal.InternalServer; public class WebTestProfile extends TestProfile { private final int port_; private final String path_; private final URL hubEndpoint_; private final SampXmlRpcClientFactory xClientFactory_; private final boolean urlControl_; private final MessageRestriction mrestrict_; private final String baseAppName_; private ClientAuthorizer clientAuth_; private int regSeq_; public WebTestProfile( Random random, boolean urlControl, MessageRestriction mrestrict ) throws IOException { this( random, getFreePort(), "/", XmlRpcKit.getInstance().getClientFactory(), urlControl, mrestrict, "test" ); } public WebTestProfile( Random random, int port, String path, SampXmlRpcClientFactory xClientFactory, boolean urlControl, MessageRestriction mrestrict, String baseAppName ) { super( random ); port_ = port; path_ = path; xClientFactory_ = xClientFactory; urlControl_ = urlControl; mrestrict_ = mrestrict; baseAppName_ = baseAppName; clientAuth_ = ClientAuthorizers.createFixedClientAuthorizer( true ); try { hubEndpoint_ = new URL( "http://" + SampUtils.getLocalhost() + ":" + port + path ); } catch ( MalformedURLException e ) { throw new AssertionError(); } } public void setClientAuthorizer( ClientAuthorizer clientAuth ) { clientAuth_ = clientAuth; } public HubProfile createHubProfile() throws IOException { WebHubProfile.ServerFactory sxfact = new WebHubProfile.ServerFactory(); sxfact.setPort( port_ ); sxfact.setXmlrpcPath( path_ ); sxfact.setOriginAuthorizer( OriginAuthorizers.TRUE ); sxfact.setAllowFlash( false ); sxfact.setAllowSilverlight( false ); ClientAuthorizer copyAuth = new ClientAuthorizer() { public void authorize( HttpServer.Request request, Map securityMap ) throws SampException { clientAuth_.authorize( request, securityMap ); } }; return new WebHubProfile( sxfact, copyAuth, mrestrict_, new KeyGenerator( "wk:", 24, createRandom() ), urlControl_ ); } public boolean isHubRunning() { try { Socket sock = new Socket( hubEndpoint_.getHost(), port_ ); sock.close(); return true; } catch ( IOException e ) { return false; } } public HubConnection register() throws SampException { int regSeq; synchronized ( this ) { regSeq = ++regSeq_; } Map secMap = new HashMap(); secMap.put( "samp.name", baseAppName_ + "-" + regSeq ); try { return new WebHubConnection( xClientFactory_ .createClient( hubEndpoint_ ), secMap ); } catch ( SampException e ) { for ( Throwable ex = e; ex != null; ex = ex.getCause() ) { if ( ex instanceof ConnectException ) { return null; } } throw e; } catch ( ConnectException e ) { return null; } catch ( IOException e ) { throw new SampException( e ); } } private static int getFreePort() throws IOException { ServerSocket sock = new ServerSocket( 0 ); int port = sock.getLocalPort(); sock.close(); return port; } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/xmlrpc/000077500000000000000000000000001356450004300234655ustar00rootroot00000000000000jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/xmlrpc/StandardHubProfileTest.java000066400000000000000000000055301356450004300307130ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; import junit.framework.TestCase; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.hub.BasicHubService; import org.astrogrid.samp.hub.HubService; import org.astrogrid.samp.hub.MessageRestriction; import org.astrogrid.samp.hub.ProfileToken; public class StandardHubProfileTest extends TestCase { private static final ProfileToken TEST_PROFILE = new ProfileToken() { public String getProfileName() { return "StandardTest"; } public MessageRestriction getMessageRestriction() { return null; } }; public StandardHubProfileTest() { Logger.getLogger( "org.astrogrid.samp" ).setLevel( Level.WARNING ); Logger.getLogger( org.astrogrid.samp.httpd.HttpServer.class.getName() ) .setLevel( Level.SEVERE ); Logger.getLogger( StandardHubProfile.class.getName() ) .setLevel( Level.SEVERE ); } public void testRunHub() throws IOException { File tmpfile = File.createTempFile( "tmp", ".samp" ); assertTrue( tmpfile.delete() ); runHub( tmpfile ); runHub( null ); } private void runHub( File lockfile ) throws IOException { final String secret = "it's-a-secret"; XmlRpcKit xmlrpc = XmlRpcKit.INTERNAL; StandardHubProfile hubProf = new StandardHubProfile( xmlrpc.getClientFactory(), xmlrpc.getServerFactory(), lockfile, secret ); if ( lockfile != null ) { assertTrue( ! lockfile.exists() ); } final HubService hubService = new BasicHubService( new Random( 199099L ) ); hubProf.start( new ClientProfile() { public HubConnection register() throws SampException { return hubService.register( TEST_PROFILE ); } public boolean isHubRunning() { return hubService.isHubRunning(); } } ); if ( lockfile != null ) { assertEquals( secret, LockInfo.readLockFile( SampUtils .fileToUrl( lockfile ) ) .getSecret() ); } URL lockurl = hubProf.publishLockfile(); assertEquals( secret, LockInfo.readLockFile( lockurl ).getSecret() ); hubProf.stop(); if ( lockfile != null ) { assertTrue( ! lockfile.exists() ); } assertNull( LockInfo.readLockFile( lockurl ) ); } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/xmlrpc/StandardTestProfile.java000066400000000000000000000072071356450004300302570ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import java.io.File; import java.io.IOException; import java.util.Random; import org.astrogrid.samp.TestProfile; import org.astrogrid.samp.DataException; import org.astrogrid.samp.SampUtils; import org.astrogrid.samp.client.ClientProfile; import org.astrogrid.samp.client.HubConnection; import org.astrogrid.samp.client.SampException; import org.astrogrid.samp.hub.HubProfile; /** * Test Profile implementation based on the Standard Profile. * It puts the lockfile in a non-standard place though, so it can * run without interfering with any Standard Profile hub that * happens to be running at the same time. * * @author Mark Taylor * @since 4 Feb 2011 */ public class StandardTestProfile extends TestProfile { private final Random random_; private final File lockFile_; private final SampXmlRpcClientFactory hubClientFactory_; private final SampXmlRpcServerFactory hubServerFactory_; private final SampXmlRpcClientFactory clientClientFactory_; private final SampXmlRpcServerFactory clientServerFactory_; private static StandardTestProfile[] testProfiles_; public StandardTestProfile( Random random, XmlRpcKit xmlrpc ) { this( random, xmlrpc.getClientFactory(), xmlrpc.getServerFactory(), xmlrpc.getClientFactory(), xmlrpc.getServerFactory() ); } public StandardTestProfile( Random random, SampXmlRpcClientFactory hubClientFactory, SampXmlRpcServerFactory hubServerFactory, SampXmlRpcClientFactory clientClientFactory, SampXmlRpcServerFactory clientServerFactory ) { super( random ); random_ = random; hubClientFactory_ = hubClientFactory; hubServerFactory_ = hubServerFactory; clientClientFactory_ = clientClientFactory; clientServerFactory_ = clientServerFactory; File dir = new File( System.getProperty( "user.dir", "." ) ); try { lockFile_ = File.createTempFile( "samp", ".lock", dir ); lockFile_.delete(); lockFile_.deleteOnExit(); } catch ( IOException e ) { throw new RuntimeException( e ); } } public HubProfile createHubProfile() { return new StandardHubProfile( hubClientFactory_, hubServerFactory_, lockFile_, Long.toHexString( random_.nextLong() ) ); } public boolean isHubRunning() { try { return LockInfo.readLockFile( SampUtils.fileToUrl( lockFile_ ) ) != null; } catch ( IOException e ) { return false; } } public HubConnection register() throws SampException { LockInfo lockInfo; try { lockInfo = LockInfo.readLockFile( SampUtils.fileToUrl( lockFile_ ) ); if ( lockInfo == null ) { return null; } else { try { lockInfo.check(); } catch ( DataException e ) { return null; } SampXmlRpcClient xClient = clientClientFactory_.createClient( lockInfo .getXmlrpcUrl() ); return new StandardHubConnection( xClient, clientServerFactory_, lockInfo.getSecret() ); } } catch ( IOException e ) { throw new SampException( e ); } } } jsamp-jsamp-1.3.7/src/test/java/org/astrogrid/samp/xmlrpc/XmlRpcTest.java000066400000000000000000000012121356450004300263710ustar00rootroot00000000000000package org.astrogrid.samp.xmlrpc; import junit.framework.TestCase; public class XmlRpcTest extends TestCase { public void testImplementations() { assertTrue( XmlRpcKit.APACHE.isAvailable() ); assertTrue( XmlRpcKit.INTERNAL.isAvailable() ); XmlRpcKit[] impls = XmlRpcKit.KNOWN_IMPLS; for ( int i = 0; i < impls.length; i++ ) { assertTrue( impls[ i ].isAvailable() ); } assertEquals( XmlRpcKit.APACHE, XmlRpcKit.getInstanceByName( "apache" ) ); assertEquals( XmlRpcKit.INTERNAL, XmlRpcKit.getInstanceByName( "internal" ) ); } }

The client toolkit API is fully described by the javadocs. This section provides some pointers for deciding where to start looking in order to use the toolkit for a particular purpose.

If you have an application in which you would like to provide SAMP functionality, you should in most cases use the HubConnector class or one of its subclasses. It is suitable for long-running applications which wish to send and receive SAMP messages, and seamlessly handles registering and unregistering with hubs, including taking care of the case in which a running hub shuts down and another one starts up later during the application's lifetime.

The HubConnector subclasses GuiHubConnector and MessageTrackerHubConnector layer some additional graphical facilities on top of HubConnector itself, for instance automatically updated ListModels which keep track of registered clients and sent/received messages. Some ready-to-use GUI components suitable for use with these are provided in the org.astrogrid.samp.gui package, or you can roll your own. The org.astrogrid.samp.gui.HubMonitor class is a simple application which uses a HubConnector. You may find its source code useful as a template for your own applications.

Short-lived applications, for instance ones which simply wish to register, send a message and unregister again, may prefer to use the lower-level HubConnection interface directly.

In either case, an instance of the ClientProfile class is used to initiate communication with the hub. The usual way for a client to obtain a profile for communication is to call DefaultClientProfile.getProfile. By default this returns an object conforming to SAMP's Standard Profile, but other profiles could be implemented and used with the rest of the API.

The classes which provide the basic SAMP hub functionality are in the package org.astrogrid.samp.hub, and the classes required for running a Standard and Web Profile hub are in org.astrogrid.samp.xmlrpc and org.astrogrid.samp.web respectively. If you want to run a hub, either with or without a graphical display, in most cases you can do it simply by using static methods (runHub/runExternalHub) of the Hub class. This hub can be embedded within a third party application if required. For more configurability, for instance customising the graphical display or hub discovery process, you may want to look at the other classes in this package, perhaps with reference to the source code. It is possible to configure which profiles a hub runs either programmatically using Hub class methods or externally by setting the jsamp.hub.profiles system property.

The JSAMP classes log activity using the J2SE java.util.logging classes. Most messages are either at the INFO level (normal activity, e.g. details of each message sent/received) or the WARNING level (errors, things which might be cause for concern). If run under default settings, all of these messages will be written to standard error for the application. It's easy to configure this behaviour otherwise however. For details see the J2SE java.util.logging javadocs, but the short story is: to restrict logging to WARNINGs only, do

Logger.getLogger("org.astrogrid.samp").setLevel(Level.WARN);

or to turn logging off altogether, do

Logger.getLogger("org.astrogrid.samp").setLevel(Level.OFF);