/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.apache.jena.sparql.util; import org.apache.jena.atlas.lib.Lib ; import org.apache.jena.atlas.logging.Log; import org.apache.jena.sparql.ARQConstants ; import org.apache.jena.sparql.ARQInternalErrorException ; /** * Helper for loading class instances * <p> * This is primarily used as a helper by {@link MappedLoader} to dynamically * load in functions without a need to pre-register them. Since these class * names originate from URIs which may contain characters which are not valid in * Java class names this class implements a simple escaping scheme. * </p> * <h3>Escaping Scheme</h3> * <p> * Escaping is applied only to the last portion of the class name, typically * {@link MappedLoader} takes care of mapping a function library namespace * prefix into a java package name and likely only the last portion (the * function name) will require escaping. * </p> * <p> * If the first character of the class name is invalid it is replaced with * {@code F_}. If any subsequent characters are invalid they are ignored and the * subsequent valid character (if any) is promoted to upper case giving a camel * case style valid class name. * </p> * <p> * For example if the last portion of the class name were {@code foo-bar-faz} * then we would end up with an escaped class name of {@code fooBarFaz}. * </p> */ public class Loader { static public Class<?> loadClass(String classNameOrURI) { return loadClass(classNameOrURI, null); } static public Class<?> loadClass(String classNameOrURI, Class<?> requiredClass) { if (classNameOrURI == null) throw new ARQInternalErrorException("Null classNameorIRI"); if (classNameOrURI.startsWith("http:")) return null; if (classNameOrURI.startsWith("urn:")) return null; String className = classNameOrURI; if (classNameOrURI.startsWith(ARQConstants.javaClassURIScheme)) className = classNameOrURI.substring(ARQConstants.javaClassURIScheme.length()); Class<?> classObj = null; try { classObj = Class.forName(className); } catch (ClassNotFoundException ex) { // It is possible that when coming from a URI we might have // characters which aren't valid as Java identifiers // We should see if we can load the class with the escaped class // name instead String baseUri = className.substring(0, className.lastIndexOf('.') + 1); String escapedClassName = escape(className.substring(className.lastIndexOf('.') + 1)); try { classObj = Class.forName(baseUri + escapedClassName); } catch (ClassNotFoundException innerEx) { // Ignore, handled in the outer catch } if (classObj == null) { Log.warn(Loader.class, "Class not found: " + className); return null; } } if (requiredClass != null && !requiredClass.isAssignableFrom(classObj)) { Log.warn(Loader.class, "Class '" + className + "' found but not a " + Lib.classShortName(requiredClass)); return null; } return classObj; } static public Object loadAndInstantiate(String uri, Class<?> requiredClass) { Class<?> classObj = loadClass(uri, requiredClass); if (classObj == null) return null; Object module = null; try { module = classObj.newInstance(); } catch (Exception ex) { String className = uri.substring(ARQConstants.javaClassURIScheme.length()); Log.warn(Loader.class, "Exception during instantiation '" + className + "': " + ex.getMessage()); return null; } return module; } static public String escape(String className) { StringBuilder builder = new StringBuilder(); boolean upgrade = false; for (int offset = 0; offset < className.length();) { int cp = className.codePointAt(offset); if (builder.length() == 0) { if (Character.isJavaIdentifierStart(cp)) { // Start character is valid builder.append(Character.toChars(cp)); } else { // Illegal start character so use F_ as prefix builder.append("F_"); } } else { if (Character.isJavaIdentifierPart(cp)) { // Upgrade to upper case if previous character was illegal if (upgrade) { cp = Character.toUpperCase(cp); upgrade = false; } // Valid character builder.append(Character.toChars(cp)); } else { // Skip illegal characters, the next valid character will be // upgraded to upper case upgrade = true; } } offset += Character.charCount(cp); } return builder.toString(); } }