diff options
author | mkuppe | 2008-06-02 10:48:28 +0000 |
---|---|---|
committer | mkuppe | 2008-06-02 10:48:28 +0000 |
commit | 020d5136081109f9b10f6644813f38d7b8d8c117 (patch) | |
tree | a8f28ca78d2a753e718aa4a2cfc0ab8aa29f570b /providers/bundles/org.eclipse.ecf.provider.jslp/src/org | |
parent | 8beceb3506a6b77235161f36e8513afc9050382f (diff) | |
download | org.eclipse.ecf-020d5136081109f9b10f6644813f38d7b8d8c117.tar.gz org.eclipse.ecf-020d5136081109f9b10f6644813f38d7b8d8c117.tar.xz org.eclipse.ecf-020d5136081109f9b10f6644813f38d7b8d8c117.zip |
RESOLVED - bug 218310: [Discovery][jSLP] ServiceType in jSLP always starts with "_service."
https://bugs.eclipse.org/bugs/show_bug.cgi?id=218310
Diffstat (limited to 'providers/bundles/org.eclipse.ecf.provider.jslp/src/org')
3 files changed, 500 insertions, 0 deletions
diff --git a/providers/bundles/org.eclipse.ecf.provider.jslp/src/org/eclipse/ecf/provider/jslp/container/JSLPDiscoveryContainer.java b/providers/bundles/org.eclipse.ecf.provider.jslp/src/org/eclipse/ecf/provider/jslp/container/JSLPDiscoveryContainer.java new file mode 100644 index 000000000..e3efa79a0 --- /dev/null +++ b/providers/bundles/org.eclipse.ecf.provider.jslp/src/org/eclipse/ecf/provider/jslp/container/JSLPDiscoveryContainer.java @@ -0,0 +1,233 @@ +/******************************************************************************* + * Copyright (c) 2007 Versant Corp. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Markus Kuppe (mkuppe <at> versant <dot> com) - initial API and implementation + ******************************************************************************/ +package org.eclipse.ecf.provider.jslp.container; + +import ch.ethz.iks.slp.*; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.*; +import java.util.Map.Entry; +import org.eclipse.core.runtime.Assert; +import org.eclipse.ecf.core.ContainerConnectException; +import org.eclipse.ecf.core.events.*; +import org.eclipse.ecf.core.identity.*; +import org.eclipse.ecf.core.security.IConnectContext; +import org.eclipse.ecf.core.util.ECFException; +import org.eclipse.ecf.core.util.Trace; +import org.eclipse.ecf.discovery.*; +import org.eclipse.ecf.discovery.identity.IServiceID; +import org.eclipse.ecf.discovery.identity.IServiceTypeID; +import org.eclipse.ecf.discovery.service.IDiscoveryService; +import org.eclipse.ecf.internal.provider.jslp.*; +import org.eclipse.ecf.provider.jslp.identity.*; + +public class JSLPDiscoveryContainer extends AbstractDiscoveryContainerAdapter implements IDiscoveryService { + public static final String NAME = "ecf.discovery.jslp"; //$NON-NLS-1$ + + // TODO-mkuppe make this configurable via cm + public static long REDISCOVER = Long.parseLong(System.getProperty("net.slp.rediscover", new Long(60L * 1000L).toString())); //$NON-NLS-1$ + + private JSLPDiscoveryJob discoveryJob; + + private ID targetID; + + /** + * @throws IDCreateException + */ + public JSLPDiscoveryContainer() throws IDCreateException { + super(JSLPNamespace.NAME, new DiscoveryContainerConfig(IDFactory.getDefault().createStringID(JSLPDiscoveryContainer.class.getName()))); + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.core.IContainer#connect(org.eclipse.ecf.core.identity.ID, org.eclipse.ecf.core.security.IConnectContext) + */ + public void connect(ID aTargetID, IConnectContext connectContext) throws ContainerConnectException { + if (this.discoveryJob != null || getConfig() == null) { + throw new ContainerConnectException(Messages.JSLPDiscoveryContainer_0); + } + targetID = (aTargetID == null) ? getConfig().getID() : aTargetID; + fireContainerEvent(new ContainerConnectingEvent(this.getID(), aTargetID, connectContext)); + + discoveryJob = new JSLPDiscoveryJob(this); + discoveryJob.schedule(); + + fireContainerEvent(new ContainerConnectedEvent(this.getID(), aTargetID)); + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.core.IContainer#disconnect() + */ + public void disconnect() { + ID anID = getConnectedID(); + fireContainerEvent(new ContainerDisconnectingEvent(this.getID(), anID)); + targetID = null; + if (discoveryJob != null) { + discoveryJob.cancel(); + discoveryJob = null; + } + fireContainerEvent(new ContainerDisconnectedEvent(this.getID(), anID)); + } + + public void fireServiceDiscovered(IServiceInfo iinfo) { + Assert.isNotNull(iinfo); + if (getConfig() != null) { + fireServiceDiscovered(new ServiceContainerEvent(iinfo, getConfig().getID())); + } else { + Trace.trace(Activator.PLUGIN_ID, JSLPDebugOptions.METHODS_TRACING, this.getClass(), "fireServiceDiscovered(IServiceInfo iinfo)", "This IContainer is already disposed thus shouldn't fire events anymore"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + public void fireServiceTypeDiscovered(IServiceTypeID serviceTypeID) { + Assert.isNotNull(serviceTypeID); + if (getConfig() != null) { + fireServiceTypeDiscovered(new ServiceTypeContainerEvent(serviceTypeID, getConfig().getID())); + } else { + Trace.trace(Activator.PLUGIN_ID, JSLPDebugOptions.METHODS_TRACING, this.getClass(), "fireServiceTypeDiscovered(IServiceInfo iinfo)", "This IContainer is already disposed thus shouldn't fire events anymore"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + public void fireServiceUndiscovered(IServiceInfo iinfo) { + Assert.isNotNull(iinfo); + if (getConfig() != null) { + fireServiceUndiscovered(new ServiceContainerEvent(iinfo, getConfig().getID())); + } else { + Trace.trace(Activator.PLUGIN_ID, JSLPDebugOptions.METHODS_TRACING, this.getClass(), "fireServiceTypeDiscovered(IServiceInfo iinfo)", "This IContainer is already disposed thus shouldn't fire events anymore"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.core.IContainer#getConnectedID() + */ + public ID getConnectedID() { + return targetID; + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServiceInfo(org.eclipse.ecf.discovery.identity.IServiceID) + */ + public IServiceInfo getServiceInfo(IServiceID service) { + Assert.isNotNull(service); + IServiceInfo[] services = getServices(service.getServiceTypeID()); + for (int i = 0; i < services.length; i++) { + IServiceInfo serviceInfo = services[i]; + JSLPServiceID sid = null; + try { + sid = (JSLPServiceID) IDFactory.getDefault().createID(getConnectNamespace(), new Object[] {service}); + } catch (IDCreateException e1) { + Trace.catching(Activator.PLUGIN_ID, JSLPDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "getServiceInfo(IServiceID)", e1); //$NON-NLS-1$ + continue; + } + String host = serviceInfo.getLocation().getHost(); + try { + if (InetAddress.getByName(host).equals(sid.getAddress())) { + return serviceInfo; + } + } catch (UnknownHostException e) { + Trace.catching(Activator.PLUGIN_ID, JSLPDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "getServiceInfo(IServiceID)", e); //$NON-NLS-1$ + continue; + } + } + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServiceTypes() + */ + public IServiceTypeID[] getServiceTypes() { + List result = new ArrayList(); + try { + ServiceLocationEnumeration slenum = Activator.getDefault().findServiceTypes(null, null); + for (; slenum.hasMoreElements();) { + ServiceType st = new ServiceType((String) slenum.nextElement()); + IServiceID sid = (IServiceID) getConnectNamespace().createInstance(new Object[] {st, ""}); //$NON-NLS-1$ + result.add(sid.getServiceTypeID()); + } + } catch (ServiceLocationException e) { + Trace.catching(Activator.PLUGIN_ID, JSLPDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "getServiceTypes(int)", e); //$NON-NLS-1$ + } catch (IDCreateException e) { + Trace.catching(Activator.PLUGIN_ID, JSLPDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "getServiceTypes(int)", e); //$NON-NLS-1$ + } + return (IServiceTypeID[]) result.toArray(new IServiceTypeID[result.size()]); + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServices() + */ + public IServiceInfo[] getServices() { + try { + return convertToIServiceInfo(Activator.getDefault().getServiceURLs()); + } catch (ServiceLocationException e) { + Trace.catching(Activator.PLUGIN_ID, JSLPDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "getServices(int)", e); //$NON-NLS-1$ + } + return new IServiceInfo[0]; + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServices(org.eclipse.ecf.discovery.identity.IServiceTypeID) + */ + public IServiceInfo[] getServices(IServiceTypeID type) { + Assert.isNotNull(type); + try { + JSLPServiceID sid = (JSLPServiceID) IDFactory.getDefault().createID(getConnectNamespace(), new Object[] {type, null}); + JSLPServiceTypeID stid = (JSLPServiceTypeID) sid.getServiceTypeID(); + return convertToIServiceInfo(Activator.getDefault().getServiceURLs(stid.getServiceType(), Arrays.asList(stid.getScopes())), type.getScopes()); + } catch (IDCreateException e) { + Trace.catching(Activator.PLUGIN_ID, JSLPDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "getServices(IServiceTypeID)", e); //$NON-NLS-1$ + } catch (ServiceLocationException e) { + Trace.catching(Activator.PLUGIN_ID, JSLPDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "getServices(int)", e); //$NON-NLS-1$ + } + return new IServiceInfo[0]; + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#registerService(org.eclipse.ecf.discovery.IServiceInfo) + */ + public void registerService(IServiceInfo aServiceInfo) throws ECFException { + Assert.isNotNull(aServiceInfo); + try { + JSLPServiceInfo si = new JSLPServiceInfo(aServiceInfo); + IServiceTypeID stid = si.getServiceID().getServiceTypeID(); + Activator.getDefault().register(si.getServiceURL(), Arrays.asList(stid.getScopes()), new ServicePropertiesAdapter(si.getServiceProperties()).toProperties()); + } catch (ServiceLocationException e) { + Trace.catching(Activator.PLUGIN_ID, JSLPDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "registerService(IServiceInfo)", e); //$NON-NLS-1$ + throw new ECFException(e.getMessage(), e); + } + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#unregisterService(org.eclipse.ecf.discovery.IServiceInfo) + */ + public void unregisterService(IServiceInfo aServiceInfo) throws ECFException { + Assert.isNotNull(aServiceInfo); + JSLPServiceInfo si = new JSLPServiceInfo(aServiceInfo); + try { + Activator.getDefault().deregister(si.getServiceURL()); + } catch (ServiceLocationException e) { + Trace.catching(Activator.PLUGIN_ID, JSLPDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "unregisterService(IServiceInfo)", e); //$NON-NLS-1$ + } + } + + private IServiceInfo[] convertToIServiceInfo(Map serviceURLs) { + return convertToIServiceInfo(serviceURLs, new String[0]); + } + + private IServiceInfo[] convertToIServiceInfo(Map serviceURLs, String[] scopes) { + List tmp = new ArrayList(); + for (Iterator itr = serviceURLs.entrySet().iterator(); itr.hasNext();) { + Map.Entry entry = (Entry) itr.next(); + ServiceURL url = (ServiceURL) entry.getKey(); + //TODO-mkuppe https://bugs.eclipse.org/230182 + IServiceInfo serviceInfo = new JSLPServiceInfo(new ServiceURLAdapter(url, scopes), -1, -1, new ServicePropertiesAdapter((List) entry.getValue())); + tmp.add(serviceInfo); + } + return (IServiceInfo[]) tmp.toArray(new IServiceInfo[tmp.size()]); + } +} diff --git a/providers/bundles/org.eclipse.ecf.provider.jslp/src/org/eclipse/ecf/provider/jslp/identity/JSLPNamespace.java b/providers/bundles/org.eclipse.ecf.provider.jslp/src/org/eclipse/ecf/provider/jslp/identity/JSLPNamespace.java new file mode 100644 index 000000000..49745b265 --- /dev/null +++ b/providers/bundles/org.eclipse.ecf.provider.jslp/src/org/eclipse/ecf/provider/jslp/identity/JSLPNamespace.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2007 Versant Corp. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Markus Kuppe + ******************************************************************************/ +package org.eclipse.ecf.provider.jslp.identity; + +import ch.ethz.iks.slp.ServiceType; +import ch.ethz.iks.slp.ServiceURL; +import org.eclipse.ecf.core.identity.*; +import org.eclipse.ecf.discovery.identity.*; +import org.eclipse.ecf.internal.provider.jslp.Messages; + +public class JSLPNamespace extends Namespace { + private static final String JSLP_SCHEME = "jslp"; //$NON-NLS-1$ + + private static final long serialVersionUID = -3041453162456476102L; + + public static final String NAME = "ecf.namespace.slp"; //$NON-NLS-1$ + + /* (non-Javadoc) + * @see org.eclipse.ecf.core.identity.Namespace#createInstance(java.lang.Object[]) + */ + public ID createInstance(Object[] parameters) throws IDCreateException { + // error case + if (parameters == null || parameters.length < 1 || parameters.length > 2) { + throw new IDCreateException(Messages.JSLPNamespace_2); + + // error case + } else if (parameters[0] == null || parameters[0].equals("")) { //$NON-NLS-1$ + throw new IDCreateException(Messages.JSLPNamespace_3); + + // create by jSLP ServiceURL + } else if (parameters[0] instanceof ServiceURL) { + ServiceURL anURL = (ServiceURL) parameters[0]; + IServiceTypeID stid = new JSLPServiceTypeID(this, anURL, (String[]) parameters[1]); + return new JSLPServiceID(this, stid, anURL.getHost()); + + // conversion call where conversion isn't necessary + } else if (parameters[0] instanceof JSLPServiceID) { + return (ID) parameters[0]; + + } else if (parameters[0] instanceof IServiceID) { + IServiceID anId = (IServiceID) parameters[0]; + parameters[0] = anId.getServiceTypeID(); + return createInstance(parameters); + + // create by ECF discovery generic IServiceTypeID (but not JSLPServiceID!!!) + } else if (parameters[0] instanceof IServiceTypeID) { + IServiceTypeID stid = (IServiceTypeID) parameters[0]; + parameters[0] = stid.getName(); + return createInstance(parameters); + + // create by jSLP ServiceType + } else if (parameters[0] instanceof ServiceType) { + IServiceTypeID stid = new JSLPServiceTypeID(this, (ServiceType) parameters[0]); + return new JSLPServiceID(this, stid, (String) parameters[1]); + + // create by jSLP ServiceType String representation (from external) + } else if (parameters[0] instanceof String && ((String) parameters[0]).startsWith("service:")) { //$NON-NLS-1$ + parameters[0] = new ServiceType((String) parameters[0]); + return createInstance(parameters); + + // create by ECF discovery generic String representation + } else if (parameters[0] instanceof String && ((String) parameters[0]).startsWith("_")) { //$NON-NLS-1$ + String type = (String) parameters[0]; + String name = (String) parameters[1]; + IServiceTypeID stid = new JSLPServiceTypeID(this, new ServiceTypeID(this, type)); + return new JSLPServiceID(this, stid, name); + + // create by "jslp:..." + } else if (parameters[0] instanceof String && ((String) parameters[0]).startsWith(getScheme() + Namespace.SCHEME_SEPARATOR)) { + String str = (String) parameters[0]; + int index = str.indexOf(Namespace.SCHEME_SEPARATOR); + parameters[0] = str.substring(index + 1); + return createInstance(parameters); + + // error case second parameter not a String + } else if (parameters.length == 2 && parameters[1] != null && !(parameters[1] instanceof String)) { + throw new IDCreateException(Messages.JSLPNamespace_4); + + // error case + } else { + throw new IDCreateException(Messages.JSLPNamespace_3); + } + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.core.identity.Namespace#getScheme() + */ + public String getScheme() { + return JSLP_SCHEME; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ecf.core.identity.Namespace#getSupportedParameterTypesForCreateInstance() + */ + public Class[][] getSupportedParameterTypes() { + return new Class[][] { {String.class}, {String.class, String.class}, {ServiceURL.class}, {IServiceTypeID.class}, {IServiceID.class}, {ServiceType.class, String.class}}; + } +} diff --git a/providers/bundles/org.eclipse.ecf.provider.jslp/src/org/eclipse/ecf/provider/jslp/identity/JSLPServiceTypeID.java b/providers/bundles/org.eclipse.ecf.provider.jslp/src/org/eclipse/ecf/provider/jslp/identity/JSLPServiceTypeID.java new file mode 100644 index 000000000..6fa33c8c6 --- /dev/null +++ b/providers/bundles/org.eclipse.ecf.provider.jslp/src/org/eclipse/ecf/provider/jslp/identity/JSLPServiceTypeID.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2007 Versant Corp. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Markus Kuppe (mkuppe <at> versant <dot> com) - initial API and implementation + ******************************************************************************/ + +package org.eclipse.ecf.provider.jslp.identity; + +import ch.ethz.iks.slp.ServiceType; +import ch.ethz.iks.slp.ServiceURL; +import org.eclipse.core.runtime.Assert; +import org.eclipse.ecf.core.identity.IDCreateException; +import org.eclipse.ecf.core.identity.Namespace; +import org.eclipse.ecf.core.util.StringUtils; +import org.eclipse.ecf.discovery.identity.IServiceTypeID; +import org.eclipse.ecf.discovery.identity.ServiceTypeID; +import org.eclipse.ecf.internal.provider.jslp.Messages; +import org.eclipse.osgi.util.NLS; + +public class JSLPServiceTypeID extends ServiceTypeID { + + private static final String JSLP_DELIM = ":"; //$NON-NLS-1$ + + private static final long serialVersionUID = -4558132760112793805L; + + private ServiceType st; + + protected JSLPServiceTypeID(final Namespace namespace, final String type) throws IDCreateException { + super(namespace); + try { + st = new ServiceType(type); + // verify that the ServiceType is proper + Assert.isNotNull(st.toString()); + Assert.isTrue(!st.toString().equals("")); //$NON-NLS-1$ + + final String na = st.getNamingAuthority(); + String str = st.toString(); + if (na.equals("")) { //$NON-NLS-1$ + namingAuthority = DEFAULT_NA; + } else { + namingAuthority = na; + // remove the naming authority from the string + str = replaceAllIgnoreCase(str, "." + na, ""); //$NON-NLS-1$//$NON-NLS-2$ + } + + services = StringUtils.split(str, JSLP_DELIM); + scopes = DEFAULT_SCOPE; //TODO-mkuppe https://bugs.eclipse.org/218308 + protocols = DEFAULT_PROTO; //TODO-mkuppe https://bugs.eclipse.org/230182 + + createType(); + } catch (Exception e) { + throw new IDCreateException(NLS.bind(Messages.JSLPServiceTypeID_4, type)); + } + } + + JSLPServiceTypeID(final Namespace namespace, final ServiceURL anURL, final String[] scopes) { + this(namespace, anURL.getServiceType()); + + if (scopes != null && scopes.length > 0) { + this.scopes = scopes; + } + + // set the protocol if provided + String protocol = anURL.getProtocol(); + if (protocol != null) { + protocols = new String[] {protocol}; + createType(); + } + } + + JSLPServiceTypeID(final Namespace namespace, final IServiceTypeID type) { + super(namespace, type); + + StringBuffer buf = new StringBuffer("service:"); //$NON-NLS-1$ + for (int i = 0; i < services.length; i++) { + buf.append(services[i]); + // #228876 + if (!namingAuthority.equalsIgnoreCase(DEFAULT_NA) && i == 1) { + buf.append("."); //$NON-NLS-1$ + buf.append(namingAuthority); + } + buf.append(":"); //$NON-NLS-1$ + } + // remove dangling colon + String string = buf.toString(); + st = new ServiceType(string.substring(0, string.length() - 1)); + } + + JSLPServiceTypeID(Namespace namespace, ServiceType aServiceType) { + super(namespace); + st = aServiceType; + + final String na = st.getNamingAuthority(); + String str = st.toString(); + if (na.equals("")) { //$NON-NLS-1$ + namingAuthority = DEFAULT_NA; + } else { + namingAuthority = na; + // remove the naming authority from the string + str = replaceAllIgnoreCase(str, "." + na, ""); //$NON-NLS-1$//$NON-NLS-2$ + } + + services = StringUtils.split(str.substring(8), JSLP_DELIM); + scopes = DEFAULT_SCOPE; //TODO-mkuppe https://bugs.eclipse.org/218308 + protocols = DEFAULT_PROTO; //TODO-mkuppe https://bugs.eclipse.org/230182 + + createType(); + } + + /** + * @return the jSLP ServiceType + */ + public ServiceType getServiceType() { + return st; + } + + /* (non-Javadoc) + * @see org.eclipse.ecf.discovery.identity.ServiceTypeID#getInternal() + */ + public String getInternal() { + final String str = st.toString(); + Assert.isNotNull(str); + + // remove the dangling colon if present + if (str.endsWith(":")) { //$NON-NLS-1$ + Assert.isTrue(str.length() > 1); + return str.substring(0, str.length() - 1); + } + + // remove the default naming authority #228876 + return replaceAllIgnoreCase(str, "." + DEFAULT_NA, ""); //$NON-NLS-1$//$NON-NLS-2$ + } + + /** + * Returns the string resulting from replacing all occurrences of the target with the replace + * string. Note that the target matches literally but ignoring the case, and this is not the same behavior as the + * String.replaceAll, which uses regular expressions for doing the matching. + * @param string the start string. Must not be <code>null</code>. + * @param target the target to search for in the start string. Must not be <code>null</code>. + * @param replace the replacement string to substitute when the target is found. Must not be <code>null</code>. + * @return String result. Will not be <code>null</code>. If target is not found in the given string, + * then the result will be the entire input string. + * + * @see StringUtils#replaceAll(String, String, String) but case insensitive + */ + //TODO-mkuppe https://bugs.eclipse.org/233807 + public static String replaceAllIgnoreCase(String string, String target, String replace) { + final int index = string.toLowerCase().indexOf(target.toLowerCase()); + if (index == -1) + return string; + return string.substring(0, index) + replace + replaceAllIgnoreCase(string.substring(index + target.length()), target, replace); + } + +} |