Added: aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/ClassDeclaration.java URL: http://svn.apache.org/viewvc/aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/ClassDeclaration.java?rev=1179673&view=auto ============================================================================== --- aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/ClassDeclaration.java (added) +++ aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/ClassDeclaration.java Thu Oct 6 15:47:50 2011 @@ -0,0 +1,568 @@ +/* + * 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.aries.versioning.utils; + +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; + + +public class ClassDeclaration extends GenericDeclaration +{ + + // Binary Compatibility - deletion of package-level access field/method/constructors of classes and interfaces in the package + // will not break binary compatibility when an entire package is updated. + + // Assumptions: + // 1. This tool assumes that the deletion of package-level fields/methods/constructors is not break binary compatibility + // based on the assumption of the entire package is updated. + // + + + + private final String superName; + private final String[] interfaces; + private final Map<String, FieldDeclaration> fields; + private final Map<String, Set<MethodDeclaration>> methods; + + private final Map<String, Set<MethodDeclaration>> methodsInUpperChain = new HashMap<String, Set<MethodDeclaration>>(); + private final Map<String, Collection<FieldDeclaration>> fieldsInUpperChain = new HashMap<String, Collection<FieldDeclaration>>(); + private final Collection<String> supers = new ArrayList<String> (); + + + private final URLClassLoader jarsLoader; + + private final BinaryCompatibilityStatus binaryCompatible = new BinaryCompatibilityStatus(true, null); + public Map<String, FieldDeclaration> getFields() + { + return fields; + } + /** + * Get the methods in the current class plus the methods in the upper chain + * @return + */ + public Map<String, Set<MethodDeclaration>> getAllMethods() { + + Map<String, Set<MethodDeclaration>> methods = new HashMap<String, Set<MethodDeclaration>>(getMethods()); + methods.putAll(getMethodsInUpperChain()); + return methods; + } + + public Map<String, Set<MethodDeclaration>> getMethods() + { + return methods; + } + + + public ClassDeclaration(int access, String name, String signature, String superName, + String[] interfaces, URLClassLoader loader) + { + super(access, name, signature); + this.superName = superName; + this.interfaces = interfaces; + this.fields = new HashMap<String, FieldDeclaration>(); + this.methods = new HashMap<String, Set<MethodDeclaration>>(); + this.jarsLoader = loader; + } + + private void getFieldsRecursively(String superClass) { + + if ((superClass != null) ) { + // load the super class of the cd + try { + SemanticVersioningClassVisitor svc = new SemanticVersioningClassVisitor(jarsLoader); + ClassReader cr = new ClassReader(jarsLoader.getResourceAsStream(superClass + SemanticVersioningUtils.classExt)); + if (cr != null) { + cr.accept(svc, 0); + ClassDeclaration cd = svc.getClassDeclaration(); + if (cd != null) { + addFieldInUpperChain(cd.getFields()); + getFieldsRecursively(cd.getSuperName()); + for (String iface : cd.getInterfaces()) { + getFieldsRecursively(iface); + } + } + } + } catch (IOException ioe) { + // not a problem + } + } + } + + private void getMethodsRecursively(String superClass) + { + if ((superClass != null) ) { + // load the super class of the cd + SemanticVersioningClassVisitor svc = new SemanticVersioningClassVisitor(jarsLoader); + // use URLClassLoader to load the class + try { + ClassReader cr = new ClassReader(jarsLoader.getResourceAsStream(superClass + SemanticVersioningUtils.classExt)); + if (cr != null) { + cr.accept(svc, 0); + ClassDeclaration cd = svc.getClassDeclaration(); + if (cd != null) { + addMethodsInUpperChain(cd.getMethods()); + getMethodsRecursively(cd.getSuperName()); + for (String iface : cd.getInterfaces()) { + getMethodsRecursively(iface); + } + } + } + } catch (IOException ioe) { + // not a deal + } + } + } + + + public Map<String, Collection<FieldDeclaration>> getFieldsInUpperChain() { + if (fieldsInUpperChain.isEmpty()) { + getFieldsRecursively(getSuperName()); + for (String ifs : getInterfaces()) { + getFieldsRecursively(ifs); + } + } + return fieldsInUpperChain; + } + + private void addFieldInUpperChain(Map<String, FieldDeclaration> fields) { + for (Map.Entry<String, FieldDeclaration> field : fields.entrySet()) { + String fieldName = field.getKey(); + Set<FieldDeclaration> fds = new HashSet<FieldDeclaration>(); + if (fieldsInUpperChain.get(fieldName) != null) { + fds.addAll(fieldsInUpperChain.get(fieldName)); + } + + fds.add(fields.get(fieldName)); + fieldsInUpperChain.put(fieldName, fds); + } + } + public Map<String, Set<MethodDeclaration>> getMethodsInUpperChain() { + if (methodsInUpperChain.isEmpty()) { + getMethodsRecursively(getSuperName()); + for (String ifs : getInterfaces()) { + getMethodsRecursively(ifs); + } + } + return methodsInUpperChain; + } + + private void addMethodsInUpperChain(Map<String, Set<MethodDeclaration>> methods) { + for (Map.Entry<String, Set<MethodDeclaration>> method : methods.entrySet()) { + String methodName = method.getKey(); + Set<MethodDeclaration> mds = new HashSet<MethodDeclaration>(); + if (methodsInUpperChain.get(methodName) != null) { + mds.addAll(methodsInUpperChain.get(methodName)); + } + mds.addAll(method.getValue()); + methodsInUpperChain.put(methodName, mds); + } + } + public Collection<String> getUpperChainRecursively(String className) { + Collection<String> clazz = new HashSet<String>(); + + if (className!= null) { + // load the super class of the cd + SemanticVersioningClassVisitor svc = new SemanticVersioningClassVisitor(jarsLoader); + try { + ClassReader cr = new ClassReader(jarsLoader.getResourceAsStream(className + SemanticVersioningUtils.classExt)); + cr.accept(svc, 0); + clazz.add(className); + if (svc.getClassDeclaration() != null) { + String superName = svc.getClassDeclaration().getSuperName(); + className = superName; + clazz.addAll(getUpperChainRecursively(superName)); + if (svc.getClassDeclaration().getInterfaces() != null) { + for (String iface : svc.getClassDeclaration().getInterfaces()) { + clazz.addAll(getUpperChainRecursively(iface)); + } + } + } + } catch (IOException ioe) { + // not to worry about this. terminate. + } + } + return clazz; + } + + public Collection<String> getAllSupers() { + if (supers.isEmpty()) { + supers.addAll(getUpperChainRecursively(getSuperName())); + for (String iface : getInterfaces()) { + supers.addAll(getUpperChainRecursively(iface)); + } + } + return supers; + } + public String getSuperName() + { + return superName; + } + public String[] getInterfaces() + { + return interfaces; + } + + public void addFields(FieldDeclaration fd) { + fields.put(fd.getName(), fd); + } + + + public void addMethods(MethodDeclaration md) + { + String key = md.getName(); + Set<MethodDeclaration> overloaddingMethods = methods.get(key); + if (overloaddingMethods != null) { + overloaddingMethods.add(md); + methods.put(key, overloaddingMethods); + } else { + Set<MethodDeclaration> mds = new HashSet<MethodDeclaration>(); + mds.add(md); + methods.put(key, mds); + } + } + + + public BinaryCompatibilityStatus getBinaryCompatibleStatus(ClassDeclaration old) { + // check class signature, fields, methods + if (old == null) { + return binaryCompatible; + } + BinaryCompatibilityStatus bcs = getClassSignatureBinaryCompatibleStatus(old); + if (!!!bcs.isCompatible()) { + return bcs; + } else { + bcs = getAllMethodsBinaryCompatibleStatus(old); + if (!!!bcs.isCompatible()) { + return bcs; + } else { + bcs = getFieldsBinaryCompatibleStatus(old); + if (!!!bcs.isCompatible()) { + return bcs; + } else { + bcs = areAllSuperPresent(old); + if (!!!bcs.isCompatible()) { + return bcs; + } + } + } + } + return binaryCompatible; + + } + + + public boolean isAbstract() { + return Modifier.isAbstract(getAccess()); + } + + private BinaryCompatibilityStatus getClassSignatureBinaryCompatibleStatus(ClassDeclaration originalClass) { + // if a class was not abstract but changed to abstract + // not final changed to final + // public changed to non-public + StringBuilder reason = new StringBuilder("The class " + getName() ); + boolean compatible = false; + if (!!!originalClass.isAbstract() && isAbstract()) { + reason.append(" was not abstract but is changed to be abstract.") ; + } else if (!!!originalClass.isFinal() && isFinal()){ + reason.append( " was not final but is changed to be final."); + } else if (originalClass.isPublic() && !!! isPublic()) { + reason.append(" was public but is changed to be non-public."); + } else { + compatible = true; + } + return new BinaryCompatibilityStatus(compatible, compatible? null: reason.toString()); + } + + public BinaryCompatibilityStatus getFieldsBinaryCompatibleStatus(ClassDeclaration originalClass) { + // for each field to see whether the same field has changed + // not final -> final + // static <-> nonstatic + + for (Map.Entry<String, FieldDeclaration> entry : originalClass.getFields().entrySet()) { + + FieldDeclaration bef_fd = entry.getValue(); + FieldDeclaration cur_fd = getFields().get(entry.getKey()); + + String fieldName = bef_fd.getName(); + //only interested in the public or protected fields + boolean compatible = true; + if (bef_fd.isPublic() || bef_fd.isProtected()) { + StringBuilder reason = new StringBuilder("The public or protected field " +fieldName); + + if (cur_fd == null) { + reason.append(" has been deleted."); + compatible = false; + } else if ((!!!bef_fd.isFinal()) && (cur_fd.isFinal())) { + // make sure it has not been changed to final + reason.append(" was not final but has been changed to be final."); + compatible = false; + + } else if (bef_fd.isStatic() != cur_fd.isStatic()) { + // make sure it the static signature has not been changed + reason.append( " was static but is changed to be non static or vice versa."); + compatible = false; + } + // check to see the field type is the same + else if (!!!Type.getType(bef_fd.getDesc()).equals(Type.getType(cur_fd.getDesc()))) { + reason.append(" has different type."); + compatible = false; + + } else if (SemanticVersioningUtils.isLessAccessible(bef_fd, cur_fd)) { + // check whether the new field is less accessible than the old one + reason.append(" is less accessible."); + compatible = false; + } + if (!!!compatible) { + return new BinaryCompatibilityStatus(compatible, reason.toString()); + } + } + } + // need to check the newly added fields do not cause binary compatibility issue: + // e.g. the new fields has less access than the old one + // the new field is static(instance) while the old one is instance(static) respectively. + Collection<String> curFields = getFields().keySet(); + Collection<String> oldFields = originalClass.getFields().keySet(); + curFields.removeAll(oldFields); + Map<String, Collection<FieldDeclaration>> superFields = new HashMap<String, Collection<FieldDeclaration>>(); + if (!!!(curFields.isEmpty())) { + superFields = getFieldsInUpperChain(); + } + // for each new field we need to find out whether it may cause binary incompatibility + for ( String newFieldName : curFields) { + // check whether the new field has the same field name in the super with the same type + boolean existInSuper = false; + if (superFields.containsKey(newFieldName)) { + FieldDeclaration newfd = getFields().get(newFieldName); + Collection<FieldDeclaration> superfd = superFields.get(newFieldName); + FieldDeclaration oldfd = null; + if ((superfd != null) ) { + for (FieldDeclaration fd : superfd) { + if (newfd.equals(fd)) { + oldfd = fd; + existInSuper = true; + break; + } + } + } + if ((existInSuper) && ((SemanticVersioningUtils.isLessAccessible(oldfd, newfd)) || (oldfd.isStatic() != newfd.isStatic()))){ + + return new BinaryCompatibilityStatus(false, "The new field " + newfd.getName() + " conflicts with the same field in its super class. For more details, check the Binary Compatibility section(Chapter 13) of the Java Specification."); + } + } + } + return binaryCompatible; + } + + private BinaryCompatibilityStatus getAllMethodsBinaryCompatibleStatus(ClassDeclaration originalClass) { + // for all methods + // no methods should have deleted + // method return type has not changed + // method changed from not abstract -> abstract + Map<String, Set<MethodDeclaration>> oldMethods = originalClass.getMethods(); + Map<String, Set<MethodDeclaration>> newMethods = getMethods(); + return areMethodsBinaryCompatible(oldMethods, newMethods) ; + } + + public BinaryCompatibilityStatus areMethodsBinaryCompatible( + Map<String, Set<MethodDeclaration>> oldMethods, Map<String, Set<MethodDeclaration>> newMethods) + { + + Map<String, Collection<MethodDeclaration>> extraMethods = new HashMap<String, Collection<MethodDeclaration>>(); + + for (Map.Entry<String, Set<MethodDeclaration>> me : newMethods.entrySet()) { + Collection<MethodDeclaration> mds = new ArrayList<MethodDeclaration>(me.getValue()); + extraMethods.put(me.getKey(), mds); + } + + for (Map.Entry<String, Set<MethodDeclaration>> methods : oldMethods.entrySet()) { + // all overloading methods, check against the current class + String methodName = methods.getKey(); + Collection<MethodDeclaration> oldMDSigs = methods.getValue(); + // If the method cannot be found in the current class, it means that it has been deleted. + Collection<MethodDeclaration> newMDSigs = newMethods.get(methodName); + // for each overloading methods + outer: for (MethodDeclaration md : oldMDSigs) { + String mdName = md.getName(); + StringBuilder reason = new StringBuilder("The " + SemanticVersioningUtils.getReadableMethodSignature(mdName, md.getDesc()) ); + if (md.isProtected() || md.isPublic()) { + if (newMDSigs != null) { + // try to find it in the current class + for (MethodDeclaration new_md : newMDSigs) { + // find the method with the same return type, parameter list + if ((md.equals(new_md))) { + // If the old method is final but the new one is not or vice versa + // If the old method is static but the new one is non static + // If the old method is not abstract but the new is + + + boolean compatible = true; + if ( !!!Modifier.isFinal(md.getAccess()) && !!!Modifier.isStatic(md.getAccess()) && Modifier.isFinal(new_md.getAccess())) { + compatible = false; + reason.append(" was not final but has been changed to be final."); + } else if (Modifier.isStatic(md.getAccess()) != Modifier.isStatic(new_md.getAccess())){ + compatible = false; + reason.append(" has changed from static to non-static or vice versa."); + } else if ((Modifier.isAbstract(new_md.getAccess()) == true) && (Modifier.isAbstract(md.getAccess()) == false)) { + compatible = false; + reason.append( " has changed from non abstract to abstract. "); + } + else if (SemanticVersioningUtils.isLessAccessible(md, new_md)) { + compatible = false; + reason.append(" is less accessible."); + } + if (!!!compatible) { + return new BinaryCompatibilityStatus(compatible, reason.toString()); + } + else { + // remove from the extra map + Collection<MethodDeclaration> mds = extraMethods.get(methodName); + mds.remove(new_md); + extraMethods.put(methodName, mds); + continue outer; + } + } + } + } + + // + // if we are here, it means that we have not found the method with the same description and signature + // which means that the method has been deleted. Let's make sure it is not moved to its upper chain. + if (!!!isMethodInSuperClass(md)) { + + reason.append(" has been deleted or its return type or parameter list has changed."); + return new BinaryCompatibilityStatus(false, reason.toString()); + + + } else { + if (newMDSigs != null) { + for (MethodDeclaration new_md : newMDSigs) { + // find the method with the same return type, parameter list + if ((md.equals(new_md))) { + Collection<MethodDeclaration> mds = extraMethods.get(methodName); + mds.remove(new_md); + extraMethods.put(methodName, mds); + } + } + } + } + } + } + } + + // Check the newly added method has not caused binary incompatibility + for (Map.Entry<String, Collection<MethodDeclaration>> extraMethodSet : extraMethods.entrySet()){ + for (MethodDeclaration md : extraMethodSet.getValue()) { + if (isNewMethodSpecialCase(md)){ + String reason = "The newly added " + SemanticVersioningUtils.getReadableMethodSignature(md.getName(), md.getDesc()) + " conflicts with the same method in its super class. For more details, check the Binary Compatibility section(Chapter 13) of the Java Specification."; + return new BinaryCompatibilityStatus(false, reason); + } + } + } + + + return binaryCompatible; + } + public MethodDeclaration getExtraMethods(ClassDeclaration old ) { + // Need to find whether there are new abstract methods added. + + if (Modifier.isAbstract(getAccess())) { + Map<String, Set<MethodDeclaration>> currMethodsMap = getAllMethods(); + + Map<String, Set<MethodDeclaration>> oldMethodsMap = old.getAllMethods(); + // only interested in an abstract class + + for (Map.Entry<String, Set<MethodDeclaration>> currMethod : currMethodsMap.entrySet()) { + String methodName = currMethod.getKey(); + Collection<MethodDeclaration> newMethods = currMethod.getValue(); + + // for each abstract method, we look for whether it exists in the old class + Collection<MethodDeclaration> oldMethods = oldMethodsMap.get(methodName); + for (MethodDeclaration new_md : newMethods) { + if (oldMethods == null) { + return new_md; + } else { + if (oldMethods.contains(new_md)) { + continue; + } else { + return new_md; + } + } + } + } + // if we reach here, it means we have scanned all methods in the new classes. All of them are in the original class. No new method is added! + return null; + } + // not to worry as it is not abstract class:o + return null; + } + + public boolean isMethodInSuperClass(MethodDeclaration md){ + // scan the super class and interfaces + String methodName = md.getName(); + Collection<MethodDeclaration> overloaddingMethods = getMethodsInUpperChain().get(methodName); + if (overloaddingMethods != null) { + for (MethodDeclaration value : overloaddingMethods) { + // method signature and name same and also the method should not be less accessible + if (md.equals(value) && (!!!SemanticVersioningUtils.isLessAccessible(md, value)) && (value.isStatic()==md.isStatic())) { + return true; + } + } + } + return false; + } + + /** + * The newly added method is less accessible than the old one in the super or is a static (respectively instance) method. + * @param md + * @return + */ + public boolean isNewMethodSpecialCase(MethodDeclaration md){ + // scan the super class and interfaces + String methodName = md.getName(); + Collection<MethodDeclaration> overloaddingMethods = getMethodsInUpperChain().get(methodName); + if (overloaddingMethods != null) { + for (MethodDeclaration value : overloaddingMethods) { + // method signature and name same and also the method should not be less accessible + if (md.equals(value) && (SemanticVersioningUtils.isLessAccessible(md, value) || value.isStatic()!=md.isStatic())) { + return true; + } + } + } + return false; + } + public BinaryCompatibilityStatus areAllSuperPresent(ClassDeclaration old) { + Collection<String> oldSupers = old.getAllSupers(); + boolean containsAll = getAllSupers().containsAll(oldSupers); + if (!!!containsAll) { + oldSupers.removeAll(getAllSupers()); + return new BinaryCompatibilityStatus(false, "The superclasses or superinterfaces have stopped being super: " + oldSupers.toString()); + } + return binaryCompatible; + } +}
Added: aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/FieldDeclaration.java URL: http://svn.apache.org/viewvc/aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/FieldDeclaration.java?rev=1179673&view=auto ============================================================================== --- aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/FieldDeclaration.java (added) +++ aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/FieldDeclaration.java Thu Oct 6 15:47:50 2011 @@ -0,0 +1,64 @@ +/* + * 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.aries.versioning.utils; + + +public class FieldDeclaration extends GenericDeclaration +{ + private String desc; + FieldDeclaration(int access, String name, String desc, String signature) { + super(access, name, signature); + this.desc = desc; + } + + public String getDesc() + { + return desc; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((getName()== null) ? 0 : getName().hashCode()); + result = prime * result + ((desc == null) ? 0 : desc.hashCode()); + result = prime * result + ((getSignature() == null) ? 0 : getSignature().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (getClass() != obj.getClass()) return false; + FieldDeclaration other = (FieldDeclaration) obj; + if (getName() == null) { + if (other.getName() != null) return false; + } else if (!getName().equals(other.getName())) return false; + if (desc == null) { + if (other.desc != null) return false; + } else if (!desc.equals(other.desc)) return false; + if (getSignature() == null) { + if (other.getSignature() != null) return false; + } else if (!getSignature().equals(other.getSignature())) return false; + return true; + } + +} Added: aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/GenericDeclaration.java URL: http://svn.apache.org/viewvc/aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/GenericDeclaration.java?rev=1179673&view=auto ============================================================================== --- aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/GenericDeclaration.java (added) +++ aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/GenericDeclaration.java Thu Oct 6 15:47:50 2011 @@ -0,0 +1,107 @@ +/* + * 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.aries.versioning.utils; + +import java.lang.reflect.Modifier; + +import org.objectweb.asm.Opcodes; + +public abstract class GenericDeclaration +{ + + private final int access; + private final String name; + private final String signature; + + public GenericDeclaration(int access, String name, String signature) { + int updatedAccess = access; + // Ignore the native or synchronized modifier as they do not affect binary compatibility + if (Modifier.isNative(access)) { + updatedAccess = updatedAccess - Opcodes.ACC_NATIVE; + } + if (Modifier.isSynchronized(access)) { + updatedAccess = updatedAccess - Opcodes.ACC_SYNCHRONIZED; + } + this.access = access; + this.name = name; + this.signature = signature; + } + public int getAccess() + { + return access; + } + + public String getName() + { + return name; + } + + + + public String getSignature() + { + return signature; + } + + public boolean isFinal() { + return Modifier.isFinal(access); + } + + public boolean isStatic() { + return Modifier.isStatic(access); + } + + public boolean isPublic() { + return Modifier.isPublic(access); + } + + public boolean isProtected() { + return Modifier.isProtected(access); + } + public boolean isPrivate() { + return Modifier.isPrivate(access); + } + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + access; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((signature == null) ? 0 : signature.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + GenericDeclaration other = (GenericDeclaration) obj; + if (access != other.access) return false; + if (name == null) { + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; + if (signature == null) { + if (other.signature != null) return false; + } else if (!signature.equals(other.signature)) return false; + return true; + } + +} \ No newline at end of file Added: aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/MethodDeclaration.java URL: http://svn.apache.org/viewvc/aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/MethodDeclaration.java?rev=1179673&view=auto ============================================================================== --- aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/MethodDeclaration.java (added) +++ aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/MethodDeclaration.java Thu Oct 6 15:47:50 2011 @@ -0,0 +1,69 @@ +/* + * 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.aries.versioning.utils; + +import java.lang.reflect.Modifier; + + +public class MethodDeclaration extends GenericDeclaration +{ + private final String desc; + MethodDeclaration(int access, String name, String desc, String signature, String[] exceptions) { + super(access, name, signature); + this.desc = desc; + } + public String getDesc() + { + return desc; + } + + public boolean isAbstract() { + return Modifier.isAbstract(getAccess()); + } + @Override + public int hashCode() + { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((desc == null) ? 0 : desc.hashCode()); + result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); + result = prime * result + ((getSignature() == null) ? 0 : getSignature().hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (getClass() != obj.getClass()) return false; + MethodDeclaration other = (MethodDeclaration) obj; + if (desc == null) { + if (other.desc != null) return false; + } else if (!desc.equals(other.desc)) return false; + if (getName() == null) { + if (other.getName() != null) return false; + } else if (!getName().equals(other.getName())) return false; + if (getSignature() == null) { + if (other.getSignature() != null) return false; + } else if (!getSignature().equals(other.getSignature())) return false; + return true; + } + +} Added: aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/SemanticVersioningClassVisitor.java URL: http://svn.apache.org/viewvc/aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/SemanticVersioningClassVisitor.java?rev=1179673&view=auto ============================================================================== --- aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/SemanticVersioningClassVisitor.java (added) +++ aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/SemanticVersioningClassVisitor.java Thu Oct 6 15:47:50 2011 @@ -0,0 +1,134 @@ +/* + * 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.aries.versioning.utils; + +import java.lang.reflect.Modifier; +import java.net.URLClassLoader; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +public class SemanticVersioningClassVisitor implements ClassVisitor +{ + + private ClassDeclaration classDeclaration; + private boolean needVisit = false; + private URLClassLoader loader = null; + public SemanticVersioningClassVisitor(URLClassLoader newJarLoader) { + this.loader = newJarLoader; + } + + public ClassDeclaration getClassDeclaration() + { + return classDeclaration; + } + /* + * (non-Javadoc) + * + * @see org.objectweb.asm.ClassAdapter#visit(int, int, + * java.lang.String, java.lang.String, java.lang.String, java.lang.String) + */ + // visit the header of the class + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + // only interested in public class + if (Modifier.isPublic(access) || (Modifier.isProtected(access))) { + classDeclaration = new ClassDeclaration(access, name, signature, superName, interfaces, loader); + needVisit = true; + + } + } + /* + * (non-Javadoc) + * + * @see org.objectweb.asm.ClassAdapter#visitField(int, java.lang.String, + * java.lang.String, java.lang.String, java.lang.Object) + * + * Grab all protected or public fields + */ + @Override + public FieldVisitor visitField(int access, String name, String desc, + String signature, Object value) { + if (needVisit) { + FieldDeclaration fd = new FieldDeclaration(access, name, desc, signature); + + classDeclaration.addFields(fd); + } + return null; + } + + /* + * (non-Javadoc) + * + * @see org.objectweb.asm.ClassAdapter#visitMethod(int, java.lang.String, + * java.lang.String, java.lang.String, java.lang.String[]) + * Get all non-private methods + */ + @Override + public MethodVisitor visitMethod(int access, String name, String desc, + String signature, String[] exceptions) { + + + if (needVisit) { + MethodDeclaration md = new MethodDeclaration(access, name, desc, signature, exceptions); + classDeclaration.addMethods(md); + } + return null; + } + @Override + public AnnotationVisitor visitAnnotation(String arg0, boolean arg1) + { + return null; + } + @Override + public void visitAttribute(Attribute arg0) + { + // no-op + } + + @Override + public void visitEnd() + { + //no-op + + } + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) + { + //no-op + //The inner class will be scanned on its own. However, the method level class will be excluded, as they won't be public or protected. + + } + @Override + public void visitOuterClass(String owner, String name, String desc) + { + //no op + + } + @Override + public void visitSource(String arg0, String arg1) + { + //no-op + + } + + +} Added: aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/SemanticVersioningUtils.java URL: http://svn.apache.org/viewvc/aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/SemanticVersioningUtils.java?rev=1179673&view=auto ============================================================================== --- aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/SemanticVersioningUtils.java (added) +++ aries/trunk/versioning/versioning-checker/src/main/java/org/apache/aries/versioning/utils/SemanticVersioningUtils.java Thu Oct 6 15:47:50 2011 @@ -0,0 +1,176 @@ +/* + * 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.aries.versioning.utils; + +import java.util.HashMap; +import java.util.Map; + +public class SemanticVersioningUtils +{ + + public static String classExt = ".class"; + public static String javaExt = ".java"; + public static String schemaExt = ".xsd"; + public static String jarExt = ".jar"; + + public static boolean isLessAccessible(GenericDeclaration before, GenericDeclaration after) { + + if (before.getAccess() == after.getAccess()) { + return false; + } + //When it reaches here, the two access are different. Let's make sure the whether the after field has less access than the before field. + if (before.isPublic() ){ + if (!!!after.isPublic()) { + return true; + } + } else if (before.isProtected()) { + if (!!!(after.isPublic() || after.isProtected())) { + return true; + } + } else { + if (!!!before.isPrivate()) { + // the field is package level. + if (after.isPrivate()) { + return true; + } + } + } + + return false; + } + + /** + * Transform ASM method desc to a human readable form + * e.g. + * void m(int i, float f) <= (IF)V + * int m(Object o) <= (Ljava/lang/Object;)I + * int[] m(int i, String s) <= (ILjava/lang/String;)[I + * Object m(int[] i) <= ([I)Ljava/lang/Object; + * @param methodName method name + * @param methodDesc binary form method description + * @return + */ + public static String getReadableMethodSignature( String methodName, String methodDesc) { + // need to find the return type first, which is outside the () + int lastBrace = methodDesc.lastIndexOf(")"); + String constructorSymbol = "<init>"; + // parameter + StringBuilder methodSignature = new StringBuilder(); + if (lastBrace == -1) { + // This is odd, don't attempt to transform. Just return back. Won't happen unless byte code weaving is not behaving. + return "method " + methodName + methodDesc; + } + String param = methodDesc.substring(1, lastBrace); + if (constructorSymbol.equals(methodName)) { + //This means the method is a constructor. In the binary form, the constructor carries a name 'init'. Let's use the source + // code proper name + methodSignature.append("constructor with parameter list "); + } else { + String returnType = methodDesc.substring(lastBrace + 1); + methodSignature.append("method "); + methodSignature.append(transform(returnType)); + methodSignature.append(" "); + methodSignature.append(methodName); + } + // add the paramether list + methodSignature.append("("); + methodSignature.append(transform(param)); + methodSignature.append(")"); + return methodSignature.toString(); + } + + /** + * ASM Type descriptor look up table + * @author emily + * + */ + private enum TypeDescriptor{ + I("int"), Z("boolean"), C("char"), B("byte"), + S("short"), F("float"), J("long"), D("double"), V("void"); + + String desc; + TypeDescriptor(String desc) { + this.desc = desc; + } + String getDesc(){ + return desc; + } + private static final Map<String, TypeDescriptor> stringToEnum = new HashMap<String, TypeDescriptor>(); + static { + for (TypeDescriptor td : values()) { + stringToEnum.put(td.toString(), td); + } + } + public static TypeDescriptor fromString(String symbol) { + return stringToEnum.get(symbol); + } + + } + private static String transform(String asmDesc) + { + String separator = ", "; + int brkCount = 0; + StringBuilder returnStr = new StringBuilder(); + //remove the '['s + + while (asmDesc.length() > 0) { + while(asmDesc.startsWith("[")){ + asmDesc = asmDesc.substring(1); + brkCount ++; + } + while (asmDesc.startsWith("L")) { + //remove the L and ; + int semiColonIndex = asmDesc.indexOf(";"); + + + + returnStr.append(asmDesc.substring(1, semiColonIndex)); + asmDesc = asmDesc.substring(semiColonIndex + 1); + for (int index =0; index < brkCount; index ++) { + returnStr.append("[]"); + } + brkCount =0; + returnStr.append(separator); + } + + TypeDescriptor td = null; + while ( (asmDesc.length() > 0) && ( td = TypeDescriptor.fromString(asmDesc.substring(0,1))) != null) { + + returnStr.append(td.getDesc()); + for (int index =0; index < brkCount; index ++) { + returnStr.append("[]"); + } + brkCount =0; + returnStr.append(separator); + asmDesc = asmDesc.substring(1); + } + + + } + String finalStr = returnStr.toString(); + if (finalStr.endsWith(separator)){ + finalStr = finalStr.substring(0, finalStr.lastIndexOf(separator)); + } + return finalStr; + } + + + +}
