/*
* Copyright (c) 2008, 2010, Zepheira All 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 net.enilink.komma.generator;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.enilink.composition.annotations.Iri;
import net.enilink.composition.mappers.RoleMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import net.enilink.commons.iterator.IExtendedIterator;
import net.enilink.vocab.rdf.RDF;
import net.enilink.komma.literals.LiteralConverter;
import net.enilink.komma.core.IEntityManager;
import net.enilink.komma.core.INamespace;
import net.enilink.komma.core.IQuery;
import net.enilink.komma.core.IReference;
import net.enilink.komma.core.IStatement;
import net.enilink.komma.core.URI;
public class JavaNameResolverImpl implements JavaNameResolver {
private final Logger log = LoggerFactory.getLogger(JavaNameResolver.class);
private static final String FILTER_REGEX_PATTERN = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"
+ "ASK { ?thing rdf:type ?type . "
+ " FILTER regex(str(?thing), ?pattern, \"i\")}";
/** namespace -> package */
@Inject
@Named("packages")
private Map<String, String> packages = new HashMap<String, String>();
/** namespace -> prefix */
private Map<String, String> prefixes = new HashMap<String, String>();
private Map<URI, URI> aliases = new HashMap<URI, URI>();
private RoleMapper<URI> roles;
@Inject
private LiteralConverter literalConverter;
private ClassLoaderPackages cl;
private Set<String> nouns = new HashSet<String>();
private IEntityManager manager;
private static class ClassLoaderPackages extends ClassLoader {
public ClassLoaderPackages(ClassLoader parent) {
super(parent);
}
@Override
public Package[] getPackages() {
return super.getPackages();
}
}
public JavaNameResolverImpl() {
this(Thread.currentThread().getContextClassLoader());
}
public JavaNameResolverImpl(ClassLoader cl) {
this.cl = new ClassLoaderPackages(cl);
}
@Inject
public void setRoleMapper(RoleMapper<URI> roles) {
this.roles = roles;
}
@Inject
public void setManager(IEntityManager manager) {
this.manager = manager;
Set<String> localNames = new HashSet<String>();
for (INamespace ns : manager.getNamespaces()) {
bindPrefixToNamespace(ns.getPrefix(), ns.getURI().toString());
}
IExtendedIterator<IStatement> stmts = manager.matchAsserted(null, null,
null);
try {
while (stmts.hasNext()) {
IReference subj = stmts.next().getSubject();
if (subj.getURI() != null) {
localNames.add(subj.getURI().localPart());
}
}
} finally {
stmts.close();
}
for (String name : localNames) {
if (!name.matches(".[A-Z_-]")) {
nouns.add(name.toLowerCase());
}
}
}
public void assignAlias(URI name, URI alias) {
aliases.put(name, alias);
}
public void assignAnonymous(URI name) {
aliases.put(name, null);
}
public void bindPrefixToNamespace(String prefix, String namespace) {
if (prefix == null || prefix.length() == 0) {
prefixes.remove(namespace);
} else {
prefixes.put(namespace, enc(prefix));
}
}
public URI getType(URI name) {
if (aliases.containsKey(name)) {
return aliases.get(name);
}
return name;
}
public String getClassName(URI name) {
if (name == null) {
return Object.class.getName();
}
if (!packages.containsKey(name.namespace().toString())) {
Class<?> javaClass = findJavaClass(name);
if (javaClass != null) {
// TODO support n-dimension arrays
if (javaClass.isArray()) {
return javaClass.getComponentType().getName() + "[]";
}
if (javaClass.getPackage() != null) {
return javaClass.getName();
}
}
}
String pkg = getPackageName(name);
String simple = getSimpleName(name);
if (pkg == null) {
return simple;
}
return pkg + '.' + simple;
}
public String getMethodName(URI name) {
String ns = name.namespace().toString();
String localPart = name.localPart();
if (prefixes.containsKey(ns)) {
return prefixes.get(ns) + initcap(localPart);
}
return enc(localPart);
}
public String getPackageName(URI uri) {
String packageName = packages.get(uri.toString());
if (packageName != null) {
return packageName;
}
packageName = packages.get(uri.namespace().toString());
if (packageName != null) {
return packageName;
}
Class<?> javaClass = findJavaClass(uri);
if (javaClass == null || javaClass.getPackage() == null) {
log.error("Unable to determine Java class for {}", uri);
return null;
}
return javaClass.getPackage().getName();
}
public String getPropertyName(URI name) {
String ns = name.namespace().toString();
String localPart = name.localPart();
if (prefixes.containsKey(ns))
return prefixes.get(ns) + initcap(localPart);
return enc(localPart);
}
public String getPluralPropertyName(URI name) {
String ns = name.namespace().toString();
String localPart = name.localPart();
if (prefixes.containsKey(ns)) {
return prefixes.get(ns) + plural(initcap(localPart));
}
return plural(enc(localPart));
}
public String getSimpleName(URI name) {
return initcap(name.localPart());
}
private String enc(String str) {
if (str.length() == 0) {
return "_";
}
char[] name = str.toCharArray();
StringBuffer sb = new StringBuffer(name.length);
for (int i = 0; i < name.length; i++) {
if (name[i] == '-' || name[i] == '.') {
name[i + 1] = Character.toUpperCase(name[i + 1]);
} else if ('A' <= name[i] && name[i] <= 'Z' || 'a' <= name[i]
&& name[i] <= 'z') {
sb.append(name[i]);
} else if (i > 0 && '0' <= name[i] && name[i] <= '9') {
sb.append(name[i]);
} else if ('*' == name[i]) {
sb.append("Star");
} else if ('#' == name[i]) {
sb.append("Hash");
} else {
sb.append('_');
}
}
return sb.toString();
}
private Class<?> findJavaClass(URI uri) {
if (uri.equals(RDF.TYPE_XMLLITERAL)) {
return literalConverter.findClass(uri);
}
Class<?> klass = findBeanClassName(uri);
if (klass != null) {
return klass;
}
klass = findLoadedMethod(uri);
if (klass != null) {
return klass;
}
return literalConverter.findClass(uri);
}
private Class<?> findBeanClassName(URI uri) {
boolean recorded = roles.isRecordedConcept(uri);
if (recorded) {
Collection<Class<?>> rs = roles.findRoles(uri,
new HashSet<Class<?>>());
for (Class<?> r : rs) {
if (r.isInterface() && uri.equals(roles.findType(r))
&& r.getSimpleName().equals(uri.localPart())) {
return r;
}
}
for (Class<?> r : rs) {
if (r.isInterface() && uri.equals(roles.findType(r))) {
return r;
}
}
}
return null;
}
private Class<?> findLoadedMethod(URI uri) {
if (cl == null)
return null;
String sn = getSimpleName(uri);
for (Package pkg : cl.getPackages()) {
if (pkg.isAnnotationPresent(Iri.class)) {
String namespace = pkg.getAnnotation(Iri.class).value();
if (uri.namespace().equals(namespace)) {
try {
return Class.forName(pkg.getName() + '.' + sn);
} catch (ClassNotFoundException e) {
continue;
}
}
}
}
return null;
}
private String plural(String singular) {
if (singular.matches(".*[A-Z_-].*")
&& !isNoun(singular.replaceAll(".*(?=[A-Z])|.*[_-]", ""))) {
return singular;
} else if (singular.endsWith("s") && !singular.endsWith("ss")) {
return singular;
} else if (singular.endsWith("ed")) {
return singular;
} else if (singular.endsWith("y") && (singular.length() > 1)) {
char c = singular.charAt(singular.length() - 2);
if (c == 'a' || c == 'o' || c == 'e' || c == 'u' || c == 'i') {
return singular + "s";
} else {
return singular.substring(0, singular.length() - 1) + "ies";
}
} else if (singular.endsWith("s") || singular.endsWith("x")) {
return singular + "es";
} else {
return singular + "s";
}
}
/**
* If this word is a thing in our repository it is a noun. An alternative is
* to use a wordnet database.
*/
private boolean isNoun(String word) {
if (nouns != null)
return nouns.contains(word);
if (manager == null)
return false;
IQuery<?> query = manager.createQuery(FILTER_REGEX_PATTERN);
query.setParameter("pattern", "[#/:]$word\\$");
return query.getBooleanResult();
}
private String initcap(String str) {
if (str.length() == 0)
return "";
char[] name = str.toCharArray();
StringBuffer sb = new StringBuffer(name.length);
for (int i = 0; i < name.length; i++) {
if (i == 0) {
sb.append(Character.toUpperCase(name[i]));
} else if (name[i] == '-' || name[i] == '.') {
name[i + 1] = Character.toUpperCase(name[i + 1]);
} else {
sb.append(name[i]);
}
}
String string = sb.toString();
if (!Character.isLetter(string.charAt(0))) {
string = "_" + string;
}
return string;
}
}