/* * Copyright (c) 2007-2009, James Leigh All rights reserved. * Copyright (c) 2011 Talis Inc., Some rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - Neither the name of the openrdf.org nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ package org.openrdf.repository.object.compiler; import info.aduna.io.FileUtil; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.io.PrintStream; import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; import java.net.ConnectException; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import org.openrdf.annotations.Iri; import org.openrdf.model.Model; import org.openrdf.model.Namespace; import org.openrdf.model.Resource; import org.openrdf.model.URI; import org.openrdf.model.impl.URIImpl; import org.openrdf.model.vocabulary.OWL; import org.openrdf.model.vocabulary.RDF; import org.openrdf.model.vocabulary.RDFS; import org.openrdf.repository.object.compiler.model.RDFClass; import org.openrdf.repository.object.compiler.model.RDFOntology; import org.openrdf.repository.object.compiler.model.RDFProperty; import org.openrdf.repository.object.compiler.source.ClassPathBuilder; import org.openrdf.repository.object.compiler.source.JavaCompiler; import org.openrdf.repository.object.exceptions.ObjectStoreConfigException; import org.openrdf.repository.object.managers.LiteralManager; import org.openrdf.repository.object.managers.RoleMapper; import org.openrdf.repository.object.managers.helpers.RoleClassLoader; import org.openrdf.repository.object.vocabulary.MSG; import org.openrdf.rio.RDFFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Converts OWL ontologies into Java source code. * * @author James Leigh * */ public class OWLCompiler { private static final String META_INF_ANNOTATIONS = "META-INF/org.openrdf.annotations"; private static final String META_INF_BEHAVIOURS = "META-INF/org.openrdf.behaviours"; private static final String META_INF_CONCEPTS = "META-INF/org.openrdf.concepts"; private static final String META_INF_DATATYPES = "META-INF/org.openrdf.datatypes"; private static final String META_INF_ONTOLOGIES = "META-INF/org.openrdf.ontologies"; private class AnnotationBuilder implements Runnable { private final RDFProperty bean; private final List<String> content; private final File target; AnnotationBuilder(File target, List<String> content, RDFProperty bean) { this.target = target; this.content = content; this.bean = bean; } public void run() { try { bean.generateAnnotationCode(target, resolver); URI uri = bean.getURI(); String pkg = resolver.getPackageName(uri); String className = resolver.getSimpleName(uri); if (pkg != null) { className = pkg + '.' + className; } synchronized (content) { logger.debug("Saving {}", className); content.add(className); annotations.add(className); } } catch (Exception exc) { logger.error("Error processing {}", bean); if (exception == null) { exception = exc; } } } } private class ConceptBuilder implements Runnable { private final RDFClass bean; private final List<String> content; private final File target; ConceptBuilder(File target, List<String> content, RDFClass bean) { this.target = target; this.content = content; this.bean = bean; } public void run() { try { bean.generateSourceCode(target, resolver); URI uri = bean.getURI(); String pkg = resolver.getPackageName(uri); String className = resolver.getSimpleName(uri); if (pkg != null) { className = pkg + '.' + className; } boolean anon = resolver.isAnonymous(uri) && bean.isEmpty(resolver); synchronized (content) { logger.debug("Saving {}", className); content.add(className); if (!anon) { concepts.add(className); } } } catch (Exception exc) { logger.error("Error processing {}", bean); if (exception == null) { exception = exc; } } } } private final class DatatypeBuilder implements Runnable { private final RDFClass bean; private final List<String> content; private final File target; DatatypeBuilder(List<String> content, RDFClass bean, File target) { this.content = content; this.bean = bean; this.target = target; } public void run() { try { for (RDFClass equivalentRdfClass : bean.getRDFClasses(OWL.EQUIVALENTCLASS)) { Class<?> equivalentJavaClass = literals.findClass(equivalentRdfClass.getURI()); if (equivalentJavaClass != null) { String equivalentJavaClassname = equivalentJavaClass.getName(); List<URI> uris = datatypes.get(equivalentJavaClassname); if (uris == null) { uris = new ArrayList<URI>(); uris.add(equivalentRdfClass.getURI()); datatypes.put(equivalentJavaClassname, uris); } uris.add(bean.getURI()); literals.addDatatype(equivalentJavaClass, bean.getURI()); return; } } bean.generateSourceCode(target, resolver); String pkg = resolver.getPackageName(bean.getURI()); String className = resolver.getSimpleName(bean.getURI()); if (pkg != null) { className = pkg + '.' + className; } synchronized (content) { logger.debug("Saving {}", className); content.add(className); datatypes.put(className, null); } } catch (Exception exc) { logger.error("Error processing {}", bean); if (exception == null) { exception = exc; } } } } private static final String JAVA_NS = "java:"; private static ClassLoader findClassLoader() { ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == null) return OWLCompiler.class.getClassLoader(); return ccl; } Runnable helper = new Runnable() { public void run() { try { for (Runnable r = queue.take(); r != helper; r = queue.take()) { r.run(); } } catch (InterruptedException e) { logger.error(e.toString(), e); } } }; final Logger logger = LoggerFactory.getLogger(OWLCompiler.class); BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); private String[] baseClasses = new String[0]; Set<String> annotations = new TreeSet<String>(); Set<String> concepts = new TreeSet<String>(); Map<String, List<URI>> datatypes = new HashMap<String, List<URI>>(); Exception exception; LiteralManager literals; private RoleMapper mapper; private String memPrefix; private Model model; /** context -> prefix -> namespace */ private Collection<Map<String, String>> ns = Collections.emptySet(); private String pkgPrefix = ""; JavaNameResolver resolver; private Map<URL, RDFFormat> ontologies; private JavaCompiler compiler = new JavaCompiler(); private final ClassLoader cl; private OwlNormalizer normalizer; private boolean pluralForms = false; private boolean resolvingPrefix = false; /** * Constructs a new compiler instance using the * existing Java classes referenced in the classpath. * */ public OWLCompiler() throws ObjectStoreConfigException { this(findClassLoader()); } /** * Constructs a new compiler instance using the * existing Java classes referenced in the classpath. * */ public OWLCompiler(ClassLoader cl) throws ObjectStoreConfigException { assert cl != null; this.cl = cl; this.mapper = new RoleMapper(); new RoleClassLoader(mapper).loadRoles(cl); this.literals = new LiteralManager(cl); } /** * Constructs a new compiler instance using the * existing Java classes referenced in the {@link RoleMapper} and * {@link LiteralManager}. * */ public OWLCompiler(RoleMapper mapper, LiteralManager literals, ClassLoader cl) { assert mapper != null && literals != null && cl != null; this.cl = cl; this.mapper = mapper; this.literals = literals; } /** * If an attempt is made to convert Set property names to their plural form. */ public boolean isPluralForms() { return pluralForms; } public void setPluralForms(boolean enabled) { this.pluralForms = enabled; } /** * If prefixes for unknown namespaces should be looked up using a Web service. */ public boolean isResolvingPrefix() { return resolvingPrefix; } public void setResolvingPrefix(boolean resolvingPrefix) { this.resolvingPrefix = resolvingPrefix; } /** * Assigns the schema that will be compiled. * * @param model contains all relevant triples */ public void setModel(Model model) { assert model != null; this.model = model; normalizer = new OwlNormalizer(new RDFDataSource(model)); normalizer.normalize(); } /** * All Java classes created will use prepend this to their package name. */ public void setPackagePrefix(String prefix) { if (prefix == null) { this.pkgPrefix = ""; } else { this.pkgPrefix = prefix; } } /** * Override all the prefixes used in the model namespaces to this one. */ public void setMemberPrefix(String prefix) { this.memPrefix = prefix; } /** * Sets the prefixes for namespaces used in each graph of the model */ public void setPrefixNamespaces(Map<URI, Map<String, String>> namespaces) { this.ns = namespaces.values(); } /** * Sets the prefixes for namespaces used in each graph of the model */ public void setPrefixNamespaces(Collection<Map<String, String>> namespaces) { this.ns = namespaces; } /** * Sets the prefixes for namespaces used in each graph of the model */ public void setNamespaces(Map<String, String> namespaces) { this.ns = Collections.singleton(namespaces); } /** * All concepts created will extend the give baseClasses. */ public void setBaseClasses(String[] baseClasses) { assert baseClasses != null; this.baseClasses = baseClasses; } /** * The given ontologies will be downloaded and included in the concept jar * as resources. */ public void setOntologies(Map<URL, RDFFormat> ontologies) { this.ontologies = ontologies; } /** * Build concepts and behaviours, compile them and save them to this jar file * * @throws IllegalArgumentException * if no concepts found * @return a ClassLoader with in jar included * @throws IOException * @throws ObjectStoreConfigException */ public ClassLoader createJar(File jar) throws IOException, ObjectStoreConfigException { File target = createTempDir(getClass().getSimpleName()); compile(target); JarPacker packer = new JarPacker(target); packer.packageJar(jar); FileUtil.deleteDir(target); return new URLClassLoader(new URL[] { jar.toURI().toURL() }, cl); } /** * Build and compile concepts and behaivours to this directory. * * @throws IllegalArgumentException * if no concepts found * @return list of compiled classes * @throws IOException * @throws ObjectStoreConfigException */ public List<String> compile(File dir) throws ObjectStoreConfigException, IOException { if (resolver == null) { resolver = buildJavaNameResolver(pkgPrefix, memPrefix, ns, model, normalizer, cl); } List<String> classes = buildJavaFiles(dir); saveConceptResources(dir); if (!classes.isEmpty()) { ClassPathBuilder cb = new ClassPathBuilder(); cb.append(getClass().getClassLoader()).append(cl); List<File> classpath = cb.toFileList(); compiler.compile(classes, dir, classpath); } return classes; } /** * Build concepts in this directory * * @throws IllegalArgumentException * if no concepts found * @return list of concept classes created * @throws IOException * @throws ObjectStoreConfigException */ public List<String> buildJavaFiles(File dir) throws ObjectStoreConfigException, IOException { if (resolver == null) { resolver = buildJavaNameResolver(pkgPrefix, memPrefix, ns, model, normalizer, cl); } if (baseClasses.length > 0) { Set<Resource> classes = model.filter(null, RDF.TYPE, OWL.CLASS) .subjects(); for (Resource o : new ArrayList<Resource>(classes)) { RDFClass bean = new RDFClass(model, o); if (bean.getURI() == null) continue; if (bean.isDatatype()) continue; if (mapper.isRecordedConcept(bean.getURI(), cl)) continue; addBaseClass(bean); } } List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 3; i++) { threads.add(new Thread(helper)); } for (Thread thread : threads) { thread.start(); } Set<String> usedNamespaces = new HashSet<String>(); List<String> content = new ArrayList<String>(); for (Resource o : model.filter(null, RDF.TYPE, RDFS.DATATYPE) .subjects()) { RDFClass bean = new RDFClass(model, o); if (bean.getURI() == null) continue; if (literals.isRecordedeType(bean.getURI())) continue; String namespace = bean.getURI().getNamespace(); usedNamespaces.add(namespace); new DatatypeBuilder(content, bean, dir).run(); } for (Resource o : model.filter(null, RDF.TYPE, OWL.ANNOTATIONPROPERTY) .subjects()) { RDFProperty bean = new RDFProperty(model, o); if (bean.getURI() == null) continue; if (mapper.isRecordedAnnotation(bean.getURI())) continue; String namespace = bean.getURI().getNamespace(); usedNamespaces.add(namespace); queue.add(new AnnotationBuilder(dir, content, bean)); } for (Resource o : model.filter(null, RDF.TYPE, OWL.CLASS).subjects()) { if (model.contains(o, RDFS.SUBCLASSOF, MSG.MESSAGE)) continue; RDFClass bean = new RDFClass(model, o); if (bean.getURI() == null) continue; if (bean.isDatatype()) continue; if (mapper.isRecordedConcept(bean.getURI(), cl)) { if ("java:".equals(bean.getURI().getNamespace())) continue; if (isComplete(bean, mapper.findRoles(bean.getURI()), resolver)) continue; } String namespace = bean.getURI().getNamespace(); usedNamespaces.add(namespace); queue.add(new ConceptBuilder(dir, content, bean)); } Set<String> methods = new HashSet<String>(); for (int i = 0, n = threads.size(); i < n; i++) { queue.add(helper); } for (String namespace : usedNamespaces) { if (JAVA_NS.equals(namespace)) continue; RDFOntology ont = findOntology(namespace); ont.generatePackageInfo(dir, namespace, resolver); String pkg = resolver.getBoundPackageName(namespace); if (pkg != null) { String className = pkg + ".package-info"; synchronized (content) { logger.debug("Saving {}", className); content.add(className); } } } for (Thread thread1 : threads) { try { thread1.join(); } catch (InterruptedException cause) { InterruptedIOException e = new InterruptedIOException(cause.getMessage()); e.initCause(cause); throw e; } } if (exception != null) try { throw exception; } catch (ObjectStoreConfigException e) { throw new ObjectStoreConfigException(e.getMessage(), e); } catch (IOException e) { throw new IOException(e.getMessage(), e); } catch (Exception e) { throw new UndeclaredThrowableException(e); } if (!methods.isEmpty()) { printClasses(methods, dir, META_INF_BEHAVIOURS); content.addAll(methods); } return content; } /** * Save META-INF resource for concepts in this parent directory. This method * must be called after {@link #buildJavaFiles(File)}. */ public void saveConceptResources(File dir) throws IOException { if (!annotations.isEmpty()) { printClasses(annotations, dir, META_INF_ANNOTATIONS); } if (!concepts.isEmpty()) { printClasses(concepts, dir, META_INF_CONCEPTS); } if (!datatypes.isEmpty()) { printDatatypes(datatypes, dir, META_INF_DATATYPES); } if (ontologies != null) { packOntologies(ontologies, dir, META_INF_ONTOLOGIES); } } private void addBaseClass(RDFClass klass) { if (klass.getRDFClasses(RDFS.SUBCLASSOF).isEmpty()) { for (String b : baseClasses) { URI name = new URIImpl(JAVA_NS + b); model.add(klass.getURI(), RDFS.SUBCLASSOF, name); } } } private boolean isComplete(RDFClass bean, Collection<Class<?>> roles, JavaNameResolver resolver) { loop: for (RDFProperty prop : bean.getDeclaredProperties()) { if (prop.getURI() == null) continue; String iri = prop.getURI().stringValue(); for (Class<?> role : roles) { for (Method m : role.getMethods()) { if (m.isAnnotationPresent(Iri.class) && iri.equals(m.getAnnotation(Iri.class).value())) continue loop; } } return false; } loop: for (RDFClass type : bean.getDeclaredMessages()) { if (type.getURI() == null || resolver.isAnonymous(type.getURI())) continue; String iri = type.getURI().stringValue(); for (Class<?> role : roles) { for (Method m : role.getMethods()) { if (m.isAnnotationPresent(Iri.class) && iri.equals(m.getAnnotation(Iri.class).value())) continue loop; } } return false; } loop: for (RDFClass sups : bean.getRDFClasses(RDFS.SUBCLASSOF)) { if (sups.getURI() == null) continue; String iri = sups.getURI().stringValue(); for (Class<?> role : roles) { for (Class<?> face : role.getInterfaces()) { if (face.isAnnotationPresent(Iri.class)) { if (iri.equals(face.getAnnotation(Iri.class).value())) continue loop; } } Class<?> parent = role.getSuperclass(); if (parent != null && parent.isAnnotationPresent(Iri.class)) { if (iri.equals(parent.getAnnotation(Iri.class).value())) continue loop; } } return false; } // TODO check annotations return true; } private File createTempDir(String name) throws IOException { String tmpDirStr = System.getProperty("java.io.tmpdir"); if (tmpDirStr != null) { File tmpDir = new File(tmpDirStr); if (!tmpDir.exists()) { tmpDir.mkdirs(); } } File tmp = File.createTempFile(name, ""); tmp.delete(); tmp.mkdir(); return tmp; } private JavaNameResolver buildJavaNameResolver(String pkgPrefix, String memberPrefix, Collection<Map<String, String>> namespaces, Model model, OwlNormalizer normalizer, ClassLoader cl) { if (model == null) throw new IllegalStateException("setModel not called"); /** namespace -> package */ Map<String, String> packages = new HashMap<String, String>(); for (String ns : findUndefinedNamespaces(model, cl)) { String prefix = findPrefix(ns, model); packages.put(ns, pkgPrefix + prefix); } JavaNameResolver resolver = createJavaNameResolver(packages, memberPrefix, namespaces, cl); for (URI uri : normalizer.getAnonymousClasses()) { resolver.assignAnonymous(uri); } for (Map.Entry<URI, URI> e : normalizer.getAliases().entrySet()) { resolver.assignAlias(e.getKey(), e.getValue()); } resolver.setImplNames(normalizer.getImplNames()); for (Resource o : model.filter(null, RDF.TYPE, OWL.CLASS).subjects()) { RDFClass bean = new RDFClass(model, o); URI uri = bean.getURI(); if (uri == null || bean.isDatatype()) continue; if (!"java:".equals(uri.getNamespace()) && mapper.isRecordedConcept(uri, cl) && !isComplete(bean, mapper.findRoles(uri), resolver)) { resolver.ignoreExistingClass(uri); String ns = uri.getNamespace(); if (!packages.containsKey(ns)) { String prefix = findPrefix(ns, model); packages.put(ns, pkgPrefix + prefix); } } } for (Map.Entry<String, String> e : packages.entrySet()) { resolver.bindPackageToNamespace(e.getValue(), e.getKey()); } return resolver; } private JavaNameResolver createJavaNameResolver( Map<String, String> packages, String memberPrefix, Collection<Map<String, String>> namespaces, ClassLoader cl) { JavaNameResolver resolver = new JavaNameResolver(cl); resolver.setPluralForms(pluralForms); resolver.setModel(model); for (Map.Entry<String, String> e : packages.entrySet()) { resolver.bindPackageToNamespace(e.getValue(), e.getKey()); } for (Namespace e : model.getNamespaces()) { resolver.bindPrefixToNamespace(e.getPrefix(), e.getName()); } if (memberPrefix == null) { for (Map<String, String> p : namespaces) { for (Map.Entry<String, String> e : p.entrySet()) { resolver.bindPrefixToNamespace(e.getKey(), e.getValue()); } } } else { for (Map.Entry<String, String> e : packages.entrySet()) { resolver.bindPrefixToNamespace(memberPrefix, e.getKey()); } } resolver.setRoleMapper(mapper); resolver.setLiteralManager(literals); return resolver; } private RDFOntology findOntology(String namespace) { if (namespace.endsWith("#")) return new RDFOntology(model, new URIImpl(namespace.substring(0, namespace.length() - 1))); return new RDFOntology(model, new URIImpl(namespace)); } private String findPrefix(String ns, Model model) { for (Namespace e : model.getNamespaces()) { if (ns.equals(e.getName()) && e.getPrefix().length() > 0) { return e.getPrefix(); } } if (resolvingPrefix) { String prefix = NamespacePrefixService.getInstance().prefix(ns); if (prefix != null && model.getNamespace(prefix) == null) { model.setNamespace(prefix, ns); return prefix; } } return "ns" + Integer.toHexString(ns.hashCode()); } private Set<String> findUndefinedNamespaces(Model model, ClassLoader cl) { Set<String> unknown = new HashSet<String>(); for (Resource subj : model.filter(null, RDF.TYPE, null).subjects()) { if (subj instanceof URI) { URI uri = (URI) subj; String ns = uri.getNamespace(); if (!mapper.isRecordedConcept(uri, cl) && !literals.isRecordedeType(uri) && !mapper.isRecordedAnnotation(uri)) { unknown.add(ns); } } } return unknown; } private void printClasses(Collection<String> roles, File dir, String entry) throws IOException { File f = new File(dir, entry); f.getParentFile().mkdirs(); PrintStream out = new PrintStream(new FileOutputStream(f)); try { for (String name : roles) { out.println(name); } } finally { out.close(); } } private void printDatatypes(Map<String, List<URI>> datatypes, File dir, String META_INF_DATATYPES) throws FileNotFoundException { File f = new File(dir, META_INF_DATATYPES); f.getParentFile().mkdirs(); PrintStream out = new PrintStream(new FileOutputStream(f)); try { for (Map.Entry<String, List<URI>> entry : datatypes.entrySet()) { StringBuilder sb = new StringBuilder(entry.getKey()); if (entry.getValue() != null) { StringBuilder temp = new StringBuilder(); for (URI uri : entry.getValue()) { if (temp.length() > 0) { temp.append(' '); } temp.append(uri.stringValue()); } if (temp.length() > 0) { sb.append('=').append(temp); } } out.println(sb); } } finally { out.close(); } } private void packOntologies(Map<URL, RDFFormat> rdfSources, File dir, String META_INF_ONTOLOGIES) throws IOException { File list = new File(dir, META_INF_ONTOLOGIES); list.getParentFile().mkdirs(); PrintStream inf = new PrintStream(new FileOutputStream(list)); try { for (URL rdf : rdfSources.keySet()) { try { RDFFormat format = rdfSources.get(rdf); String path = "META-INF/ontologies/"; URLConnection conn = rdf.openConnection(); if (format != null) { for (String type : format.getMIMETypes()) { conn.addRequestProperty("Accept", type); } } InputStream in = conn.getInputStream(); try { String name = getLocalName(rdf.toExternalForm()); if (format != null && !format.equals(RDFFormat.forFileName(name))) { name += "." + format.getDefaultFileExtension(); } File file = new File(dir, path + name); file.getParentFile().mkdirs(); OutputStream out = new FileOutputStream(file); try { int read; byte[] buf = new byte[1024]; while ((read = in.read(buf)) >= 0) { out.write(buf, 0, read); } } finally { out.close(); } inf.println(path + name); } finally { in.close(); } } catch (ConnectException exc) { throw new IOException("Cannot connect to " + rdf, exc); } } } finally { inf.close(); } } private String getLocalName(String uri) { int start = uri.indexOf('#'); int end = uri.length(); if (start >= 0 && start < end - 1) return uri.substring(start + 1, end); if (start >= 0 && start < end) { end = start; } int idx = uri.lastIndexOf('?'); if (idx >= 0) { end = idx; } start = uri.lastIndexOf('/'); if (start >= 0 && start < end - 1) return uri.substring(start + 1, end); if (start >= 0 && start < end) { end = start; } start = uri.lastIndexOf(':'); if (start >= 0 && start < end - 1) return uri.substring(start + 1, end); if (start >= 0 && start < end) { end = start; } return uri; } }