/** * OpenSpotLight - Open Source IT Governance Platform * * Copyright (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA * or third-party contributors as indicated by the @author tags or express * copyright attribution statements applied by the authors. All third-party * contributions are distributed under license by CARAVELATECH CONSULTORIA E * TECNOLOGIA EM INFORMATICA LTDA. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA * *********************************************************************** * OpenSpotLight - Plataforma de Governança de TI de Código Aberto * * Direitos Autorais Reservados (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA * EM INFORMATICA LTDA ou como contribuidores terceiros indicados pela etiqueta * @author ou por expressa atribuição de direito autoral declarada e atribuída pelo autor. * Todas as contribuições de terceiros estão distribuídas sob licença da * CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA. * * Este programa é software livre; você pode redistribuí-lo e/ou modificá-lo sob os * termos da Licença Pública Geral Menor do GNU conforme publicada pela Free Software * Foundation. * * Este programa é distribuído na expectativa de que seja útil, porém, SEM NENHUMA * GARANTIA; nem mesmo a garantia implícita de COMERCIABILIDADE OU ADEQUAÇÃO A UMA * FINALIDADE ESPECÍFICA. Consulte a Licença Pública Geral Menor do GNU para mais detalhes. * * Você deve ter recebido uma cópia da Licença Pública Geral Menor do GNU junto com este * programa; se não, escreva para: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.openspotlight.bundle.language.java.resolver; import org.objectweb.asm.Opcodes; import org.openspotlight.bundle.common.metamodel.link.AbstractTypeBind; import org.openspotlight.bundle.language.java.JavaConstants; import org.openspotlight.bundle.language.java.metamodel.link.*; import org.openspotlight.bundle.language.java.metamodel.node.*; import org.openspotlight.common.util.Strings; import org.openspotlight.graph.GraphReaderorg.openspotlight.graph.SLLink; import org.openspotlight.graph.Node; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Map; import java.util.TreeMap; import static org.openspotlight.common.util.Assertions.checkCondition; import static org.openspotlight.common.util.Assertions.checkNotNull; /** * This class should be used to insert all java relationships on the OSL Graph instead of inserting nodes and links by hand. This * class creates all necessary links and nodes, includding the implicit ones. When receiving int parameters on access parameters * inside methods, the {@link Opcodes} constants from ASM should be used. First of all, all types on the current classpath should * be added. After * * @author Luiz Fernando Teston - feu.teston@caravelatech.com */ public class JavaGraphNodeSupport { private static int findNumberOfParanetersByItsName( final String methodFullName ) { final String methodParameterPiece = methodFullName.substring(methodFullName.indexOf("(") + 1); int numberOfParameters; if (methodParameterPiece.startsWith(")")) { numberOfParameters = 0; } else { numberOfParameters = 1; for (final char c : methodParameterPiece.toCharArray()) { if (c == ',') { numberOfParameters++; } } } return numberOfParameters; } /** The using cache. */ private boolean usingCache = true; /** The current context root node. */ private final Node currentContextRootNode; /** The abstract context root node. */ private final Node abstractContextRootNode; /** The session. */ private final GraphReadGraphReader The nodes from this context. */ private final Map<String, JavaType> nodesFromThisContext = new TreeMap<String, JavaType>(); /** The nodes from abstract context. */ private final Map<String, JavaType> nodesFromAbstractContext = new TreeMap<String, JavaType>(); private final Logger logger = LoggerFactory.getLogger(JavaGraphNodeSupport.class); /** * Instantiates a new java graph node support. The abstractContextRootNode will store all implicit relationships and also all * abstract types will be there. The concrete types will be created on currentContextRootNode. The abstractContextRootNode * should be used as a "global classpath". First thing to use this class in a proper way: creates all types declared in * current classpath on the currentContextRootNode. When adding new implicit types (on throws, or parameter, or field * declarations for example) this types will be created on the abstract context in case where this types doesn't exists on * current context. * * @param session the session * @param currentContextRootNode the current context root node * @param abstractContextRootNode the abstract context root node */ public JavaGraphNodeSupport( final GraphReader sessioGraphReadertContextRootNode, final Node abstractContextRootNode ) { checkNotNull("session", session); checkNotNull("currentContextRootNode", currentContextRootNode); checkNotNull("abstractContextRootNode", abstractContextRootNode); checkCondition("correctAbstractContext", JavaConstants.ABSTRACT_CONTEXT.equals(abstractContextRootNode.getContext().getID())); this.session = session; this.currentContextRootNode = currentContextRootNode; this.abstractContextRootNode = abstractContextRootNode; } /** * Adds the extends links and also the implicit relationships. * * @param packageName the package name * @param typeName the type name * @param superPackageName the super package name * @param superTypeName the super type name * @throws Exception the exception */ public void addExtendsLinks( final String packageName, final String typeName, final String superPackageName, final String superTypeName ) throws Exception { if (logger.isDebugEnabled()) { logger.debug(" adding extends on " + packageName + "_" + typeName + " with super " + superPackageName + "_" + superTypeName); } final JavaType newType = this.addTypeOnAbstractContext(JavaType.class, packageName, typeName); final JavaType newSuperType = this.addTypeOnAbstractContext(JavaType.class, superPackageName, superTypeName); final Class<? extends SLLink> linkClass = newType instanceof JavaTypeInterface ? InterfaceExtends.class : Extends.class; session.addLink(linkClass, newType, newSuperType, false); } /** * Adds the implements links and also the implicit relationships. * * @param packageName the package name * @param typeName the type name * @param superPackageName the super package name * @param superTypeName the super type name * @throws Exception the exception */ public void addImplementsLinks( final String packageName, final String typeName, final String superPackageName, final String superTypeName ) throws Exception { if (logger.isDebugEnabled()) { logger.debug(" adding implements on " + packageName + "_" + typeName + " with super " + superPackageName + "_" + superTypeName); } final JavaType newType = this.addTypeOnAbstractContext(JavaType.class, packageName, typeName); final JavaType newSuperType = this.addTypeOnAbstractContext(JavaType.class, superPackageName, superTypeName); final Class<? extends SLLink> linkClass = newType instanceof JavaTypeInterface ? InterfaceExtends.class : Implements.class; session.addLink(linkClass, newType, newSuperType, false); } private void addImplicitPrimitiveCast( final String typeName, final String superTypeName, final int distance ) throws Exception { final JavaType type = this.addTypeOnCurrentContext(JavaTypePrimitive.class, "", typeName, Opcodes.ACC_PUBLIC); final JavaType superType = this.addTypeOnCurrentContext(JavaTypePrimitive.class, "", superTypeName, Opcodes.ACC_PUBLIC); final ImplicitPrimitiveCast link = session.addLink(ImplicitPrimitiveCast.class, type, superType, false); link.setDistance(distance); } /** * Adds the throws on method and also the implicit relationships. * * @param method the method * @param exceptionPackage the exception package * @param exceptionName the exception name * @throws Exception the exception */ public void addThrowsOnMethod( final JavaMethod method, final String exceptionPackage, final String exceptionName ) throws Exception { if (logger.isDebugEnabled()) { logger.debug(" adding throws " + exceptionPackage + "_" + exceptionName + " with super " + method.getName()); } final JavaType newExceptionType = this.addTypeOnAbstractContext(JavaType.class, exceptionPackage, exceptionName); session.addLink(MethodThrows.class, method, newExceptionType, false); } /** * Adds the type on abstract context to be used as a "global classphath element". The types declared on current classpath * should be added with method {@link #addTypeOnCurrentContext(Class, String, String, int)} * * @param nodeType the node type * @param packageName the package name * @param nodeName the node name * @return the t * @throws Exception the exception */ @SuppressWarnings( "unchecked" ) public <T extends JavaType> T addTypeOnAbstractContext( final Class<T> nodeType, final String packageName, final String nodeName ) throws Exception { if (logger.isDebugEnabled()) { logger.debug(" adding type on abstract context " + packageName + "_" + nodeName); } if (usingCache && nodesFromThisContext.containsKey(packageName + nodeName)) { return (T)nodesFromThisContext.get(packageName + nodeName); } if (usingCache && nodesFromAbstractContext.containsKey(packageName + nodeName)) { return (T)nodesFromAbstractContext.get(packageName + nodeName); } if (JavaTypePrimitive.class.equals(nodeType)) { final T newType = abstractContextRootNode.addChildNode(nodeType, nodeName); newType.setSimpleName(nodeName); newType.setQualifiedName(nodeName); return newType; } final JavaPackage newPackage = abstractContextRootNode.addChildNode(JavaPackage.class, packageName); final T newType = newPackage.addChildNode(nodeType, nodeName); newType.setSimpleName(nodeName); newType.setQualifiedName(Strings.tryToRemoveBegginingFrom(JavaConstants.DEFAULT_PACKAGE + ".", packageName + "." + nodeName.replaceAll("[$]", "."))); session.addLink(PackageType.class, newPackage, newType, false); nodesFromAbstractContext.put(packageName + nodeName, newType); if (logger.isInfoEnabled()) { logger.info("abstract ctx - added class " + nodeType.getSimpleName() + " " + packageName + "." + nodeName); } return newType; } /** * Adds the type on current context. This method should be used on current classpath elements only. The types declared outside * this classpath should be added with method {@link #addTypeOnAbstractContext(Class, String, String)}. * * @param nodeType the node type * @param packageName the package name * @param nodeName the node name * @param access the access * @return the t * @throws Exception the exception */ public <T extends JavaType> T addTypeOnCurrentContext( final Class<T> nodeType, final String packageName, final String nodeName, final int access ) throws Exception { return addTypeOnCurrentContext(nodeType, packageName, nodeName, access, null); } /** * Adds the type on current context. This method should be used on current classpath elements only. The types declared outside * this classpath should be added with method {@link #addTypeOnAbstractContext(Class, String, String)}. * * @param nodeType the node type * @param packageName the package name * @param nodeName the node name * @param access the access * @param parentType the parent type, if null will use package as parent * @return the t * @throws Exception the exception */ @SuppressWarnings( "unchecked" ) public <T extends JavaType> T addTypeOnCurrentContext( final Class<T> nodeType, final String packageName, final String nodeName, final int access, final Node parentType ) throws Exception { if (logger.isDebugEnabled()) { logger.debug(" adding type on current context " + packageName + "_" + nodeName + " with parent " + (parentType != null ? parentType.getName() : "null")); } if (usingCache && nodesFromThisContext.containsKey(packageName + nodeName)) { return (T)nodesFromThisContext.get(packageName + nodeName); } if (JavaTypePrimitive.class.equals(nodeType)) { final T newType = abstractContextRootNode.addChildNode(nodeType, nodeName); newType.setSimpleName(nodeName); newType.setQualifiedName(nodeName); return newType; } final JavaPackage newPackage = currentContextRootNode.addChildNode(JavaPackage.class, packageName); T newType = null; if (parentType != null) { newType = parentType.addChildNode(nodeType, nodeName); } else { newType = newPackage.addChildNode(nodeType, nodeName); } newType.setSimpleName(nodeName); newType.setQualifiedName(Strings.tryToRemoveBegginingFrom(JavaConstants.DEFAULT_PACKAGE + ".", packageName + "." + nodeName.replaceAll("[$]", "."))); session.addLink(PackageType.class, newPackage, newType, false); final boolean isPublic = (access & Opcodes.ACC_PUBLIC) != 0; final boolean isPrivate = (access & Opcodes.ACC_PRIVATE) != 0; final boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; final boolean isFinal = (access & Opcodes.ACC_FINAL) != 0; final boolean isProtected = (access & Opcodes.ACC_PROTECTED) != 0; newType.setPublic(isPublic); newType.setPrivate(isPrivate); newType.setStatic(isStatic); newType.setFinal(isFinal); newType.setProtected(isProtected); final JavaPackage newAbstractPackage = abstractContextRootNode.addChildNode(JavaPackage.class, packageName); final JavaType newAbstractType = newAbstractPackage.addChildNode(JavaType.class, nodeName); newAbstractType.setQualifiedName(Strings.tryToRemoveBegginingFrom(JavaConstants.DEFAULT_PACKAGE + ".", packageName + "." + nodeName.replaceAll("[$]", "."))); newAbstractType.setSimpleName(nodeName); session.addLink(PackageType.class, newPackage, newType, false); session.addLink(AbstractTypeBind.class, newAbstractType, newType, false); nodesFromThisContext.put(packageName + nodeName, newType); if (logger.isInfoEnabled()) { logger.info("added class " + nodeType.getSimpleName() + " " + packageName + "." + nodeName); } return newType; } /** * Creates the field and also its links. * * @param newType the new type * @param fieldType the field type * @param fieldPackage the field package * @param fieldTypeName the field type name * @param fieldName the field name * @param access the access * @param array the array * @param arrayDimensions the array dimensions * @return the java data field * @throws Exception the exception */ public JavaDataField createField( final JavaType newType, final Class<? extends JavaType> fieldType, final String fieldPackage, final String fieldTypeName, final String fieldName, final int access, final boolean array, final int arrayDimensions ) throws Exception { if (logger.isDebugEnabled()) { logger.debug(" adding field " + fieldPackage + "_" + fieldTypeName + " with name " + fieldName + " on parent " + (newType != null ? newType.getName() : "null")); } final JavaDataField field = newType.addChildNode(JavaDataField.class, fieldName); final JavaType fieldTypeAdded = this.addTypeOnAbstractContext(fieldType, fieldPackage, fieldTypeName); insertFieldData(field, fieldTypeAdded, access, array, arrayDimensions); return field; } /** * Creates the method and also its links. The parameters, exceptions and so on should be created with the proper methods on * this class. * * @param newType the new type * @param methodFullName the method full name * @param methodName the method name * @param constructor the constructor * @param access the access * @return the java method * @throws Exception the exception */ public JavaMethod createMethod( final JavaType newType, final String methodFullName, final String methodName, final boolean constructor, final int access ) throws Exception { if (logger.isDebugEnabled()) { logger.debug(" adding method " + methodFullName + " on parent " + (newType != null ? newType.getName() : "null")); } JavaMethod method; if (constructor) { method = newType.addChildNode(JavaMethodConstructor.class, methodFullName); } else { method = newType.addChildNode(JavaMethodMethod.class, methodFullName); } final int numberOfParameters = findNumberOfParanetersByItsName(methodFullName); method.setNumberOfParameters(numberOfParameters); method.setSimpleName(methodName); setMethodData(method, access); session.addLink(TypeDeclares.class, newType, method, false); return method; } /** * Creates the method parameter and its links. * * @param method the method * @param parameterType the parameter type * @param parameterOrder the parameter order * @param parameterPackage the parameter package * @param parameterTypeName the parameter type name * @param array the array * @param arrayDimensions the array dimensions * @throws Exception the exception */ public void createMethodParameter( final JavaMethod method, final Class<? extends JavaType> parameterType, final int parameterOrder, final String parameterPackage, final String parameterTypeName, final boolean array, final int arrayDimensions ) throws Exception { if (logger.isDebugEnabled()) { logger.debug(" adding method parameter " + parameterPackage + "_" + parameterType + " with order " + parameterOrder + " with parent " + (method != null ? method.getName() : "null")); } final JavaType methodParameterType = this.addTypeOnAbstractContext(parameterType, parameterPackage, parameterTypeName); final MethodParameterDefinition methodParametersTypeLink = session.addLink(MethodParameterDefinition.class, method, methodParameterType, false); methodParametersTypeLink.setOrder(parameterOrder); methodParametersTypeLink.setArray(array); methodParametersTypeLink.setArrayDimension(arrayDimensions); } /** * Creates the method return type and its links. * * @param method the method * @param returnType the return type * @param returnPackageName the return package name * @param returnTypeName the return type name * @param array the array * @param arrayDimension the array dimension * @throws Exception the exception */ public void createMethodReturnType( final JavaMethod method, final Class<? extends JavaType> returnType, final String returnPackageName, final String returnTypeName, final boolean array, final int arrayDimension ) throws Exception { if (logger.isDebugEnabled()) { logger.debug(" adding return " + returnPackageName + "_" + returnTypeName + " on metohd " + (method != null ? method.getName() : "null")); } final JavaType methodReturnType = this.addTypeOnAbstractContext(returnType, returnPackageName, returnTypeName); final MethodReturns methodReturnsType = session.addLink(MethodReturns.class, method, methodReturnType, false); methodReturnsType.setArray(array); methodReturnsType.setArrayDimension(arrayDimension); } /** * Insert field data. * * @param field the field * @param fieldType the field type * @param access the access * @param isArray the is array * @param dimension the dimension * @throws Exception the exception */ private void insertFieldData( final JavaDataField field, final JavaType fieldType, final int access, final boolean isArray, final int dimension ) throws Exception { final DataType fieldTypeLink = session.addLink(DataType.class, field, fieldType, false); fieldTypeLink.setArray(isArray); fieldTypeLink.setArrayDimension(dimension); final boolean isFieldPublic = (access & Opcodes.ACC_PUBLIC) != 0; final boolean isFieldPrivate = (access & Opcodes.ACC_PRIVATE) != 0; final boolean isFieldStatic = (access & Opcodes.ACC_STATIC) != 0; final boolean isFieldFinal = (access & Opcodes.ACC_FINAL) != 0; final boolean isFieldProtected = (access & Opcodes.ACC_PROTECTED) != 0; final boolean isFieldTransient = (access & Opcodes.ACC_TRANSIENT) != 0; final boolean isFieldVolatile = (access & Opcodes.ACC_VOLATILE) != 0; field.setPublic(isFieldPublic); field.setPrivate(isFieldPrivate); field.setStatic(isFieldStatic); field.setFinal(isFieldFinal); field.setProtected(isFieldProtected); field.setTransient(isFieldTransient); field.setVolatile(isFieldVolatile); } /** * Checks if is using cache. * * @return true, if is using cache */ public boolean isUsingCache() { return usingCache; } /** * Sets the method data. * * @param method the method * @param access the access */ private void setMethodData( final JavaMethod method, final int access ) { final boolean isMethodPublic = (access & Opcodes.ACC_PUBLIC) != 0; final boolean isMethodPrivate = (access & Opcodes.ACC_PRIVATE) != 0; final boolean isMethodStatic = (access & Opcodes.ACC_STATIC) != 0; final boolean isMethodFinal = (access & Opcodes.ACC_FINAL) != 0; final boolean isMethodProtected = (access & Opcodes.ACC_PROTECTED) != 0; final boolean isMethodSynchronized = (access & Opcodes.ACC_SYNCHRONIZED) != 0; method.setPublic(isMethodPublic); method.setPrivate(isMethodPrivate); method.setStatic(isMethodStatic); method.setFinal(isMethodFinal); method.setProtected(isMethodProtected); method.setSynchronized(isMethodSynchronized); } public void setupJavaTypesOnCurrentContext() throws Exception { setupWrapperAndPrimitive("byte", "java.lang", "Byte"); setupWrapperAndPrimitive("short", "java.lang", "Short"); setupWrapperAndPrimitive("int", "java.lang", "Integer"); setupWrapperAndPrimitive("long", "java.lang", "Long"); setupWrapperAndPrimitive("float", "java.lang", "Float"); setupWrapperAndPrimitive("double", "java.lang", "Double"); setupWrapperAndPrimitive("char", "java.lang", "Character"); setupWrapperAndPrimitive("boolean", "java.lang", "Boolean"); /* * here all possible conversions needs to be created because it's not * possible to find all the links between the types. For example, the * double d = 'c' doesn't work. */ // byte implicit conversions addImplicitPrimitiveCast("byte", "short", 1); addImplicitPrimitiveCast("byte", "int", 2); addImplicitPrimitiveCast("byte", "long", 3); addImplicitPrimitiveCast("byte", "float", 4); addImplicitPrimitiveCast("byte", "double", 5); // short implicit conversions addImplicitPrimitiveCast("short", "int", 1); addImplicitPrimitiveCast("short", "long", 2); addImplicitPrimitiveCast("short", "float", 3); addImplicitPrimitiveCast("short", "double", 4); // int implicit conversions addImplicitPrimitiveCast("int", "long", 1); addImplicitPrimitiveCast("int", "float", 2); addImplicitPrimitiveCast("int", "double", 3); // char and int implicit conversions addImplicitPrimitiveCast("int", "char", 4); addImplicitPrimitiveCast("char", "int", 1); // long implicit conversions addImplicitPrimitiveCast("long", "float", 1); addImplicitPrimitiveCast("long", "double", 2); // float implicit conversions addImplicitPrimitiveCast("float", "double", 1); } private void setupWrapperAndPrimitive( final String primitiveName, final String wrapperPackage, final String wrapperClass ) throws Exception { final JavaType wrapper = this.addTypeOnCurrentContext(JavaTypeClass.class, wrapperPackage, wrapperClass, Opcodes.ACC_PUBLIC); final JavaType primitive = this.addTypeOnAbstractContext(JavaTypePrimitive.class, "", primitiveName); session.addLink(Autoboxes.class, wrapper, primitive, false); session.addLink(AutoboxedBy.class, primitive, wrapper, false); } /** * Sets the using cache. * * @param usingCache the new using cache */ public void setUsingCache( final boolean usingCache ) { this.usingCache = usingCache; } }